1 Introduction

This R notebook implements the analysis of the Record-seq and RNA-seq readout of the transient diet experiments described in ‘Noninvasive assessment of gut function using transcriptional recording sentinel cells’ manuscript. The following files are stored in the data directory within the working directory:

transient-diet/
    secondaryAnalysis.Rmd
    data/
        transientDiet1_Recordseq_genomealigning.txt
        transientDiet1_Recordseq_metadata.txt
        transientDiet1_RNAseq_genomealigning.txt
        transientDiet1_RNAseq_metadata.txt
        transientDiet1_day14_RNAseq_genomealigning.txt
        transientDiet1_day14_RNAseq_metadata.txt  
        transientDiet2_Recordseq_genomealigning.txt
        transientDiet2_Recordseq_metadata.txt
        transientDiet2_RNAseq_genomealigning.txt
        transientDiet2_RNAseq_metadata.txt
        Chow.Fat.pathway.txt
        Chow.Starch.pathway.txt
        Fat.Starch.pathway.txt
    

2 Libraries

The recoRdseq package and dependencies are required for this analysis, and the fantastic patchwork package is used for visualization.

if(!require(devtools)){
  install.packages("devtools")
}
library(devtools)
if(!require(eulerr)){
  install.packages("eulerr")
}
library(eulerr)
if(!require(plyr)){
  install.packages("plyr")
}
library(plyr)
if(!require(recoRdseq)){
  install_github("plattlab/Transcriptional-Recording", subdir="recoRdseq")
}
if(!require(factoextra)){
  install.packages("factoextra")
}
library(factoextra)
if(!require(patchwork)){
  install.packages('patchwork')
}
if(!require(ggrepel)){
  install.packages('ggrepel')
}
library(recoRdseq)
library(patchwork)
library(stringr)
library(ggrepel)
colour_code = list(
  Diet = c(Chow = "#538bce", Fat="#ed915c", Starch='#42bb7f')) # we set a consistent color scheme for the three diet groups

theme_pub<-theme_minimal()+
  theme(legend.position="bottom", legend.justification="center", legend.margin=margin(0,0,0,0),legend.box.margin=margin(-10,-10,-10,-10),plot.title = element_text(hjust = 0.5), legend.spacing.y =  unit(0, 'mm'), legend.box='vertical', legend.key.size = unit(0.1, "cm"),legend.key.width = unit(0.1,"cm"), legend.text=element_text(size=5), text = element_text(size=5), panel.grid.minor = element_blank(), axis.text = element_text(size=5, colour='black'), panel.grid.major = element_line(size = 0.24, colour='gray1', linetype = 2)) # we set a consistent theme for ggplot objects

custom.config = umap.defaults
custom.config$random_state = 2

3 Transient Diet 1

Data for the transient diet experiment with 14 days is analyzed first.

3.1 Importing and pre-processing data for transient diet 1

We import the data matrices for both Record-seq and RNA-seq, filter them for lowly expressed genes as well as outlier samples with low cumulative counts, and use vst from DESeq2 to normalize and transform the data. We use a threshold of 10k counts for excluding Record-seq samples and 100k counts for excluding RNA-seq samples.

rec1<-as.data.frame(read.table("data/transientDiet1_Recordseq_genomealigning.txt", header = TRUE))
rec1d<-as.data.frame(read.table("data/transientDiet1_Recordseq_metadata.txt", header = TRUE))
DEList<-recoRdseq.preprocess(rec1, rec1d, minCountsPerSample = 10000)
rec1<-DEList[[1]]
rec1d<-DEList[[2]]
rec1d<-rec1d[rec1d$Day>1,]
rec1<-rec1[,rownames(rec1d)]
rec1_tf<-recoRdseq.transform(rec1, rec1d,transformation = 'vst')

rna1<-as.data.frame(read.table("data/transientDiet1_RNAseq_genomealigning.txt", header = TRUE))
rna1d<-as.data.frame(read.table("data/transientDiet1_RNAseq_metadata.txt", header = TRUE))
DEList<-recoRdseq.preprocess(rna1, rna1d, minCountsPerSample = 100000)
rna1<-DEList[[1]]
rna1d<-DEList[[2]]
rna1_tf<-recoRdseq.transform(rna1, rna1d)
rnadays<-unique(rna1d$Day)
rna1_day14<-as.data.frame(read.table("data/transientDiet1_day14_RNAseq_genomealigning.txt", header = TRUE))
rna1d_day14<-as.data.frame(read.table("data/transientDiet1_day14_RNAseq_metadata.txt", header = TRUE))
rna1d_day14<-rna1d_day14[,1:3]
DEList<-recoRdseq.preprocess(rna1_day14, rna1d_day14, minCountsPerSample = 100000)
rna1_day14<-DEList[[1]]
rna1d_day14<-DEList[[2]]
rna1_day14tf<-recoRdseq.transform(rna1_day14, rna1d_day14, transformation = 'vst')

3.2 Data exploration

We use Principal Component analysis and UMAP for dimensionality reduction and exploring clusters in an unsupervised fashion in our data. We first generate these for the entire dataset from Record-seq:

rec1sds <- rowSds(as.matrix(rec1_tf))
o <- order(rec1sds, decreasing = TRUE)
rec1PCA<-prcomp(t(rec1_tf[o[1:500],]))
pca_stat<-summary(rec1PCA)
pca_variance<-pca_stat$importance[2,]
rec1PCA<-as.data.frame(rec1PCA$x)
rec1PCA$Diet<-rec1d$Diet
rec1PCA$Day<-factor(rec1d$Day, levels = 1:20)
rec1PCAplot<-ggplot(rec1PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(1,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of Record-seq data - all days (transient diet 1)")


rec1UMAP<-umap(rec1PCA[,1:(ncol(rec1PCA)-2)], custom.config)
rec1UMAP<-as.data.frame(rec1UMAP$layout)
rec1UMAP$Day<-factor(rec1d$Day, levels = 2:14)
rec1UMAP$Diet<-rec1d$Diet
colnames(rec1UMAP)[1:2]<-c('UMAP1','UMAP2')
rec1UMAPplot<-ggplot(rec1UMAP, aes(x=UMAP1, y=UMAP2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.24)+theme_pub+scale_size_discrete(range=c(1,2.5))+geom_hline(yintercept = 0, size=0.24)+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+scale_fill_manual(values = as.vector(colour_code$Diet))+ggtitle("UMAP plot for Record-seq data - all days (transient diet 1)")

rec1PCAplot+rec1UMAPplot+plot_annotation(tag_levels = 'A')

For a comparison between Record-seq and RNA-seq, we check if the diet groups can be classified using PCA on day 7 (the last day when the mice are fed different diets, before switching all mice to a ‘Chow’ diet)


rec1d_day7<-rec1d[rec1d$Day==7,]
rec1_day7<-rec1[, rownames(rec1d_day7)]
rec1_day7tf<-recoRdseq.transform(rec1_day7, rec1d_day7)
rec1_day7_sds <- rowSds(as.matrix(rec1_day7tf))
o <- order(rec1_day7_sds, decreasing = TRUE)
rec1_day7PCA<-prcomp(t(rec1_day7tf[o[1:500],]))
pca_stat<-summary(rec1_day7PCA)
pca_variance<-pca_stat$importance[2,]
rec1_day7PCA<-as.data.frame(rec1_day7PCA$x)
rec1_day7PCA$Diet<-rec1d_day7$Diet
rec1_day7PCA$Day<-factor(rec1d_day7$Day, levels = c(7))
rec1_day7PCAplot<-ggplot(rec1_day7PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(2,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of Record-seq data on day 7 (transient diet 1)")

rna1_sds <- rowSds(as.matrix(rna1_tf))
o <- order(rna1_sds, decreasing = TRUE)
rna1_PCA<-prcomp(t(rna1_tf[o[1:500],]))
pca_stat<-summary(rna1_PCA)
pca_variance<-pca_stat$importance[2,]
rna1_PCA<-as.data.frame(rna1_PCA$x)
rna1_PCA$Diet<-rna1d$Diet
rna1_PCA$Day<-factor(rna1d$Day, levels = c(7))
rna1_day7PCAplot<-ggplot(rna1_PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(2,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of RNA-seq data on day 7 (transient diet 1) ")


rna1_day7PCAplot+rec1_day7PCAplot+plot_annotation(tag_levels = 'A')

Next, we check if Record-seq or RNA-seq can distinguish the samples on day 14 (7 days after switching all samples to Chow diet).


rec1d_day14<-rec1d[rec1d$Day==14,]
rec1_day14<-rec1[, rownames(rec1d_day14)]
rec1_day14tf<-recoRdseq.transform(rec1_day14, rec1d_day14)
rec1_day14_sds <- rowSds(as.matrix(rec1_day14tf))
o <- order(rec1_day14_sds, decreasing = TRUE)
rec1_day14PCA<-prcomp(t(rec1_day14tf[o[1:500],]))
pca_stat<-summary(rec1_day14PCA)
pca_variance<-pca_stat$importance[2,]
rec1_day14PCA<-as.data.frame(rec1_day14PCA$x)
rec1_day14PCA$Diet<-rec1d_day14$Diet
rec1_day14PCA$Day<-factor(rec1d_day14$Day, levels = c(14))
rec1_day14PCAplot<-ggplot(rec1_day14PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(2,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of Record-seq data on day 14 (transient diet 1)")

rna1_day14_sds <- rowSds(as.matrix(rna1_day14tf))
o <- order(rna1_day14_sds, decreasing = TRUE)
rna1_day14_PCA<-prcomp(t(rna1_day14tf[o[1:500],]))
pca_stat<-summary(rna1_day14_PCA)
pca_variance<-pca_stat$importance[2,]
rna1_day14_PCA<-as.data.frame(rna1_day14_PCA$x)
rna1_day14_PCA$Diet<-rna1d_day14$Diet
rna1_day14_PCA$Day<-factor(rna1d_day14$Day, levels = c(14))
rna1_day14PCAplot<-ggplot(rna1_day14_PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(2,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of RNA-seq data on day 14 (transient diet 1) ")


rna1_day14PCAplot+rec1_day14PCAplot+plot_annotation(tag_levels = 'A')

3.3 Discovery of differentially expressed genes (DEGs):

We identify DEGs for day 7 (since this is the final day when the mice are fed separate diets, and RNA-seq is also available for this day). We define DEGs as genes identified to be significantly differentially expressed using a threshold (padj < 0.05) by both DESeq2 and edgeR (multiple testing, since we have 3 groups).

rec1.deseq<-recoRdseq.DE(rec1_day7, rec1d_day7, tool='DESeq2')
rec1.edger<-recoRdseq.DE(rec1_day7, rec1d_day7, tool='edgeR')
rec1.deseq.genes<-recoRdseq.filterDEG(rec1.deseq, p = 0.05)
rec1.edger.genes<-recoRdseq.filterDEG(rec1.edger, p = 0.05)
rec1.DEG<-rec1.deseq[intersect(rec1.deseq.genes, rec1.edger.genes), c(1,7, which(grepl('log2FoldChange', colnames(rec1.deseq))))]
rec1.DEG$geneID<-as.character(rec1.DEG$geneID)
rec1.DEG<-rec1.DEG[order(rec1.DEG$padj),]
rec1.DEG$geneID<-as.character(rec1.DEG$geneID)
rna1.deseq<-recoRdseq.DE(rna1, rna1d, tool='DESeq2')
rna1.edger<-recoRdseq.DE(rna1, rna1d, tool='edgeR')
rna1.deseq.genes<-recoRdseq.filterDEG(rna1.deseq, p = 0.05)
rna1.edger.genes<-recoRdseq.filterDEG(rna1.edger, p = 0.05)
rna1.DEG<-rna1.deseq[intersect(rna1.deseq.genes, rna1.edger.genes), c(1,7, which(grepl('log2FoldChange', colnames(rna1.deseq))))]
rna1.DEG$geneID<-as.character(rna1.DEG$geneID)

rec1.novel<-rec1.DEG[-which(rec1.DEG$geneID%in%rna1.DEG$geneID),]

For Record-seq, we also look for DE genes over days 2-7 in Record-seq using a looser confidence threshold (padj <0.1) to identify consistent diet-signature genes.

rec1.DEG.list<-list()
rec1.er<-list()
rec1.de<-list()
rec1.global.DEG<-c()
for(i in unique(rec1d$Day)){
  if(i<8){
    dt<-rec1d[which(rec1d$Day==i), 1, drop=FALSE]
    de<-rec1[,which(colnames(rec1)%in%rownames(dt))]
    rec1.de[[i]]<-recoRdseq.DE(de,dt,tool='DESeq2')
    rec1.er[[i]]<-recoRdseq.DE(de,dt,tool='edgeR')
    rec1.de.genes<-recoRdseq.filterDEG(rec1.de[[i]], p = 0.1)
    rec1.er.genes<-recoRdseq.filterDEG(rec1.er[[i]], p = 0.1)
    rec1.DEG.list[[i]]<-rec1.de[[i]][intersect(rec1.de.genes, rec1.er.genes), c(1,7, which(grepl('log2FoldChange', colnames(rec1.de[[i]]))))]
    rec1.global.DEG<- c(rec1.global.DEG, as.character(intersect(rec1.de.genes,rec1.er.genes)))
  }
}
rec1.global.DEG1<-as.data.frame(table(rec1.global.DEG)[order(table(rec1.global.DEG), decreasing = TRUE)])
rec1.global.DEG2<-as.data.frame(table(rec1.global.DEG)[order(table(rec1.global.DEG), decreasing = TRUE)])
colnames(rec1.global.DEG1)<-c("geneID", "days_DE")
colnames(rec1.global.DEG2)<-c("geneID", "days_DE")
rec1.global.DEG1$geneID<-as.character(rec1.global.DEG1$geneID)
rec1.global.DEG2$geneID<-as.character(rec1.global.DEG2$geneID)
for(i in unique(rec1d$Day)){
  if(i<8){
  rec1.global.DEG1$V1<-rec1.de[[i]][rec1.global.DEG1$geneID,4]
  colnames(rec1.global.DEG1)[ncol(rec1.global.DEG1)]<-paste0("log2FoldChange_FC_day", i)
  rec1.global.DEG2$V1<-rec1.de[[i]][rec1.global.DEG2$geneID,8]
  colnames(rec1.global.DEG2)[ncol(rec1.global.DEG2)]<-paste0("log2FoldChange_SC_day", i)

  }
}
rec1.global.DEG1$log2FoldChange.max<-rec1.global.DEG1[,3:ncol(rec1.global.DEG1)][cbind(1:nrow(rec1.global.DEG1[,3:ncol(rec1.global.DEG1)]), max.col(replace(x <- abs(rec1.global.DEG1[,3:ncol(rec1.global.DEG1)]), is.na(x), -Inf)))]
rec1.global.DEG2$log2FoldChange.max<-rec1.global.DEG2[,3:ncol(rec1.global.DEG2)][cbind(1:nrow(rec1.global.DEG2[,3:ncol(rec1.global.DEG2)]), max.col(replace(x <- abs(rec1.global.DEG2[,3:ncol(rec1.global.DEG2)]), is.na(x), -Inf)))]

rec1.global.DEG<-rec1.global.DEG1[,c(1,2,ncol(rec1.global.DEG1))]
rec1.global.DEG$V1<-rec1.global.DEG2[,ncol(rec1.global.DEG1)]
colnames(rec1.global.DEG)[3]<-"log2FoldChange.max_FC"
colnames(rec1.global.DEG)[4]<-"log2FoldChange.max_SC"

3.4 Plotting individual DEGs:

We plot vst-transformed genome-aligning spacer counts for 6 genes in the gntR pathway for chow and starch fed mice on day 7.

gntR_genes<-c('eda','edd', 'gntT', 'kdgT', 'gntU', 'gntK')
de<-rec1d_day7[rec1d_day7$Diet!='Fat',]
dt<-rec1_day7tf[gntR_genes, rownames(de)]
rec1.gntR.plot.df<-data.frame(Diet=de$Diet, t(dt))
rec1.gntR.plot.df<-melt(rec1.gntR.plot.df, id.vars = 'Diet')
rec1.gntR.plot<-ggplot(rec1.gntR.plot.df, aes(y=value, x=variable, fill=Diet, color='black'))+geom_boxplot(size=0.24, outlier.size=0)+geom_point(size=0.48, position = position_dodge(0.75))+theme_pub+ylab("gene-aligning spacer counts (vst-transformed)")+xlab("gene")+ggtitle("Record-seq counts on day 7 for gntR gene")+scale_fill_manual(values = as.vector(colour_code$Diet[c(1,3)]))+scale_color_manual(values = c("black"), guide='none')
rec1.gntR.plot+plot_annotation()

NA
NA

3.5 Heatmap for Record-seq and RNA-seq DEGs on day 7

We plot heatmaps showing hierarchical clustering of samples using detected DE genes for both Record-seq and RNA-seq on day 7.

cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
ribosomal<-c(grep("rrs", rownames(rec1.DEG)), grep("rrl", rownames(rec1.DEG)))
if(length(ribosomal)>0){
  rec1.DEG<-rec1.DEG[-ribosomal,]
}
dheatmap<-as.data.frame(t(apply(rec1_day7tf[rec1.DEG$geneID,], 1, zscorestandardize)))
heatmap.rec1.day7<-pheatmap(dheatmap, annotation_col = rec1d_day7[,1, drop=FALSE], annotation_colors=colour_code, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = TRUE, treeheight_row = 0, clustering_distance_cols = "canberra", treeheight_col = 5,show_colnames = FALSE, show_rownames = FALSE, color = cols,fontsize_number=5, width=2.28, height=2.28, main='Record-seq DEGs day 7')

ribosomal<-c(grep("rrs", rownames(rna1.DEG)), grep("rrl", rownames(rna1.DEG)))
if(length(ribosomal)>0){
  rna1.DEG<-rna1.DEG[-ribosomal,]
}
cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
dheatmap<-as.data.frame(t(apply(rna1_tf[rna1.DEG$geneID,], 1, zscorestandardize)))
heatmap.rna1.day7<-pheatmap(dheatmap, annotation_col = rna1d[,1, drop=FALSE], annotation_colors=colour_code, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = TRUE, treeheight_row = 0, treeheight_col = 5, show_colnames = FALSE,show_rownames = FALSE, color = cols,fontsize_number=5, width=2.28, height=2.28, main='RNA-seq DEGs day 7')

3.6 Volcano plots for Record-seq DEGs

We perform pairwise DE analysis using DESeq2 and edgeR to identify log2FC and p-adj values for each diet pair on day 7, and plot volcanoes (log2FC>1.5, padj<0.1)

levels<-sort(unique(rec1d_day7[,1]))
pairwise.combo<-combn(levels, 2)
color.combo<-combn(colour_code$Diet, 2)
rec1.de.vals<-list()
rec1.ed.vals<-list()
vol.plots<-list()
DEG<-list()
for(i in 1:dim(pairwise.combo)[2]){
  ds<-rec1d_day7[which(rec1d_day7[,1]%in%pairwise.combo[,i]),]
  ds$Diet<-as.character(ds$Diet)
  dt<-rec1_day7[,rownames(ds)]
  dtf<-recoRdseq.transform(dt,ds)
  rec1.de.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'DESeq2')
  rec1.ed.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'edgeR')
  rownames(rec1.de.vals[[i]]) <- rec1.de.vals[[i]]$geneID
  rownames(rec1.ed.vals[[i]]) <- rec1.ed.vals[[i]]$geneID
  deseq.genes<-recoRdseq.filterDEG(rec1.de.vals[[i]], p = 0.1)
  edger.genes<-recoRdseq.filterDEG(rec1.ed.vals[[i]], p = 0.1)
  DEG[[i]]<-data.frame(row.names=intersect(deseq.genes, edger.genes),geneID=intersect(deseq.genes, edger.genes),log2Foldchange=rec1.de.vals[[i]][intersect(deseq.genes, edger.genes), 4], padj=rec1.de.vals[[i]][intersect(deseq.genes, edger.genes), 7])
  ribosomal<-c(grep("rrs", rownames(DEG[[i]])), grep("rrl", rownames(DEG[[i]])))
  if(length(ribosomal)>0){
      DEG[[i]]<-DEG[[i]][-ribosomal,]
  }
  DEG[[i]]$geneID<-as.character(DEG[[i]]$geneID)
  rec1.de.vals[[i]]<-rec1.de.vals[[i]][complete.cases(rec1.de.vals[[i]]),]
  rec1.de.vals[[i]]$Group<-'None'
  rec1.de.vals[[i]]$Group[ which(rec1.de.vals[[i]]$log2FoldChange>1.5&rec1.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[2]))
  rec1.de.vals[[i]]$Group[ which(rec1.de.vals[[i]]$log2FoldChange<(-1.5)&rec1.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", sort(unique(ds$Diet))[1])
 rec1.de.vals[[i]]$Group<-factor(rec1.de.vals[[i]]$Group, levels = c(paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[1])), paste0("upregulated.in.", sort(unique(ds$Diet))[2]), 'None' ))
  rec1.de.vals[[i]]$label<-FALSE
  m1<-rec1.de.vals[[i]][rec1.de.vals[[i]]$log2FoldChange>1.5, 'geneID'][1:10]
  m2<-rec1.de.vals[[i]][rec1.de.vals[[i]]$log2FoldChange<(-1.5), 'geneID'][1:10]
  m<-which(rec1.de.vals[[i]]$geneID%in%union(m1,m2))
  for(j in m){
    if(abs(rec1.de.vals[[i]]$log2FoldChange[j])>1.5&rec1.de.vals[[i]]$padj[j]<0.1){
      rec1.de.vals[[i]]$label[j]<-TRUE
    }
  }
  vol.plots[[i]]<-ggplot(rec1.de.vals[[i]], aes( x=log2FoldChange, y=(-log10(padj)), color=Group))+scale_colour_manual(values = c(color.combo[,i], 'gray70'))+geom_point(size=0.24)+geom_text_repel(data = rec1.de.vals[[i]][which(rec1.de.vals[[i]]$label),], aes( x=log2FoldChange, y=(-log10(padj)), label=geneID), size=1.76, show.legend=FALSE)+theme_pub+geom_vline(xintercept = 1.5, size=0.24)+geom_vline(xintercept = -1.5, size=0.24)+geom_hline(yintercept = 1, size=0.24)+xlab("log2 fold change")+ylab("-log10 p-adjusted value")+guides(color = guide_legend(override.aes = list(size=1.5)))
}

vol.plots[[1]] + vol.plots[[2]] +vol.plots[[3]] + plot_annotation(tag_levels = 'A')+plot_layout(ncol = 2)

3.7 Clustering on final day of experiment

We want to check whether information about diet groups prior to switch can be retrieved at day 14 - i.e 7 days after the switch. For this, we use diet-signature genes identified before the switch to hierarchically cluster the groups. Diet-signature genes are defined here as the top 10 genes by number of days (Record-seq) or p-adj value (RNA-seq) detected as enriched (log2FC > 2.5) prior to the switch. We can perfectly classify groups using Record-seq data, while for RNA-seq, the groups converge.

ribosomal<-c(grep("rrs", rec1.global.DEG$geneID), grep("rrl", rec1.global.DEG$geneID))
if(length(ribosomal)>0){
  rec1.global.DEG<-rec1.global.DEG[-ribosomal,]
}
geneShortList<-unique(c(rec1.global.DEG[which(rec1.global.DEG$log2FoldChange.max_FC>2.5), 1][1:10], rec1.global.DEG[which(rec1.global.DEG$log2FoldChange.max_SC>2.5), 1][1:10],rec1.global.DEG[which(rowMeans(rec1.global.DEG[,3:4])<(-2.5)), 1][1:10]))
cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
dheatmap<-as.data.frame(t(apply(rec1_day14tf[geneShortList,], 1, zscorestandardize)))
heatmap.rec1<-pheatmap(dheatmap, annotation_col = rec1d_day14[,1, drop=FALSE], annotation_colors=colour_code, treeheight_row=0, treeheight_col=5, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = FALSE, show_colnames = FALSE, color = cols,fontsize_number=5, main='Hierarchical clustering of Record-seq data on day 14 based on DEGS detected on day 7')

heatmap.genelist<-rec1_day14tf[geneShortList,]
colnames(heatmap.genelist)<-rec1d_day14$Diet


cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
geneShortList<-unique(c(rna1.DEG[which(rna1.DEG$log2FoldChange.Fat_vs_Chow>2.5), 1][1:12], rna1.DEG[which(rna1.DEG$log2FoldChange.Starch_vs_Chow>2.5), 1][1:12],rna1.DEG[which(rowMeans(rna1.DEG[,3:4])<(-2.5)), 1][1:12]))
dheatmap<-as.data.frame(t(apply(rna1_day14tf[geneShortList,], 1, zscorestandardize)))
dheatmap<-dheatmap[complete.cases(dheatmap),]
heatmap.rna1<-pheatmap(dheatmap, annotation_col = rna1d_day14[,1, drop=FALSE], annotation_colors=colour_code, treeheight_row=0,  treeheight_col=5, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = FALSE, show_colnames = FALSE, color = cols,fontsize_number=5, main='Hierarchical clustering of RNA-seq data on day 14 based on DEGS detected on day 7')

4 Transient Diet 2

We now analyze data for the extended transient diet experiment with 20 days.

4.1 Importing and pre-processing data for transient diet 2

We import the data matrices, filter them for lowly expressed genes as well as outlier samples with low cumulative counts, and use vst from DESeq2 to normalize and transform the data. We also exclude day1 from the analysis since we have emperically observed that the data are noisy for the first day of colonization. We use the same thresholds as the previous experiment for consistency.

rec2<-as.data.frame(read.table("data/transientDiet2_Recordseq_genomealigning.txt", header = TRUE))
rec2d<-as.data.frame(read.table("data/transientDiet2_Recordseq_metadata.txt", header = TRUE))
DEList<-recoRdseq.preprocess(rec2, rec2d, minCountsPerSample = 10000)
rec2<-DEList[[1]]
rec2d<-DEList[[2]]
rec2d<-rec2d[rec2d$Day>1,]
rec2<-rec2[,rownames(rec2d)]
rec2_tf<-recoRdseq.transform(rec2, rec2d)
rna2<-as.data.frame(read.table("data/transientDiet2_RNAseq_genomealigning.txt", header = TRUE))
rna2d<-as.data.frame(read.table("data/transientDiet2_RNAseq_metadata.txt", header = TRUE))
rna2d<-rna2d[,1:3]
DEList<-recoRdseq.preprocess(rna2, rna2d, minCountsPerSample = 100000)
rna2<-DEList[[1]]
rna2d<-DEList[[2]]
rna2_tf<-recoRdseq.transform(rna2, rna2d)
rnadays<-unique(rna2d$Day)

4.2 Data exploration

We use Principal Component analysis and UMAP for dimensionality reduction and exploring clusters in an unsupervised fashion in our data. We first generate these for the entire dataset from Record-seq:

rec2sds <- rowSds(as.matrix(rec2_tf))
o <- order(rec2sds, decreasing = TRUE)
rec2PCA<-prcomp(t(rec2_tf[o[1:500],]))
pca_stat<-summary(rec2PCA)
rec2.pca_variance<-pca_stat$importance[2,]
rec2PCA<-as.data.frame(rec2PCA$x)
rec2PCA$Diet<-rec2d$Diet
rec2PCA$Day<-factor(rec2d$Day, levels = 1:20)
rec2PCAplot<-ggplot(rec2PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(1,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(rec2.pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(rec2.pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of Record-seq data")

rec2UMAP<-umap(rec2PCA[,1:(ncol(rec2PCA)-2)], custom.config)
rec2UMAP<-as.data.frame(rec2UMAP$layout)
rec2UMAP$Day<-factor(rec2d$Day, levels = 1:20)
rec2UMAP$Diet<-rec2d$Diet
colnames(rec2UMAP)[1:2]<-c('UMAP1','UMAP2')
rec2UMAPplot<-ggplot(rec2UMAP, aes(x=UMAP1, y=UMAP2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(1,2.5))+geom_hline(yintercept = 0, size=0.24)+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+scale_fill_manual(values = as.vector(colour_code$Diet))+ggtitle("UMAP plot for Record-seq data (all days)")

rec2PCAplot+rec2UMAPplot+plot_annotation(tag_levels = 'A')

For a comparasion between Record-seq and RNA-seq, we exclude the days in Record-seq that don’t have corresponding RNA-seq data. We first check if the diet groups can be classified using PCA on day 7 (the last day when the mice are fed different diets, before switching all mice to a ‘Chow’ diet)


rec2d_day7<-rec2d[rec2d$Day==7,]
rec2_day7<-rec2[, rownames(rec2d_day7)]
rec2_day7tf<-recoRdseq.transform(rec2_day7, rec2d_day7)
rec2_day7_sds <- rowSds(as.matrix(rec2_day7tf))
o <- order(rec2_day7_sds, decreasing = TRUE)
rec2_day7PCA<-prcomp(t(rec2_day7tf[o[1:500],]))
pca_stat<-summary(rec2_day7PCA)
pca_variance<-pca_stat$importance[2,]
rec2_day7PCA<-as.data.frame(rec2_day7PCA$x)
rec2_day7PCA$Diet<-rec2d_day7$Diet
rec2_day7PCA$Day<-factor(rec2d_day7$Day, levels = c(7))
rec2_day7PCAplot<-ggplot(rec2_day7PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(2,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of Record-seq data on day 7 ")

rna2d_day7<-rna2d[rna2d$Day==7,]
rna2_day7<-rna2[, rownames(rna2d_day7)]
rna2_day7tf<-recoRdseq.transform(rna2_day7, rna2d_day7)
rna2_day7_sds <- rowSds(as.matrix(rna2_day7tf))
o <- order(rna2_day7_sds, decreasing = TRUE)
rna2_day7PCA<-prcomp(t(rna2_day7tf[o[1:500],]))
pca_stat<-summary(rna2_day7PCA)
pca_variance<-pca_stat$importance[2,]
rna2_day7PCA<-as.data.frame(rna2_day7PCA$x)
rna2_day7PCA$Diet<-rna2d_day7$Diet
rna2_day7PCA$Day<-factor(rna2d_day7$Day, levels = c(7))
rna2_day7PCAplot<-ggplot(rna2_day7PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(2,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of RNA-seq data on day 7 ")


rec2_day7PCAplot+rna2_day7PCAplot+plot_annotation(tag_levels = 'A')

We then compare the temporal trajectories of Record-seq and RNA-seq using UMAPs. Record-seq data retains information about prior diet groups till the final day, and clusters strongly based on group; whereas RNA-seq data has a pronounced temporal change, and initial clusters for different diet groups quickly converge.

rec2d_rna<-rec2d[which(rec2d$Day%in%rnadays),]
rec2_rna<-rec2[, rownames(rec2d_rna)]
rec2_rnatf<-recoRdseq.transform(rec2_rna, rec2d_rna)
rec2rnasds <- rowSds(as.matrix(rec2_rnatf))
o <- order(rec2rnasds, decreasing = TRUE)
rec2rnaPCA<-prcomp(t(rec2_rnatf[o[1:500],]))
pca_stat<-summary(rec2rnaPCA)
rec2rna.pca_variance<-pca_stat$importance[2,]
rec2rnaPCA<-as.data.frame(rec2rnaPCA$x)
rec2rnaPCA$Diet<-rec2d_rna$Diet
rec2rnaPCA$Day<-factor(rec2d_rna$Day, levels = rnadays)
rec2rnaUMAP<-umap(rec2rnaPCA[,1:(ncol(rec2rnaPCA)-2)], custom.config)
rec2rnaUMAP<-as.data.frame(rec2rnaUMAP$layout)
rec2rnaUMAP$Day<-factor(rec2d_rna$Day, levels = rnadays)
rec2rnaUMAP$Diet<-rec2d_rna$Diet
colnames(rec2rnaUMAP)[1:2]<-c('UMAP1','UMAP2')
rec2rnaUMAPplot<-ggplot(rec2rnaUMAP, aes(x=UMAP1, y=UMAP2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(1,2.5))+geom_hline(yintercept = 0, size=0.24)+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+scale_fill_manual(values = as.vector(colour_code$Diet))+ggtitle("UMAP plot for Record-seq data")

rna2sds <- rowSds(as.matrix(rna2_tf))
o <- order(rna2sds, decreasing = TRUE)
rna2PCA<-prcomp(t(rna2_tf[o[1:500],]))
pca_stat<-summary(rna2PCA)
rna2.pca_variance<-pca_stat$importance[2,]
rna2PCA<-as.data.frame(rna2PCA$x)
rna2PCA$Diet<-rna2d$Diet
rna2PCA$Day<-factor(rna2d$Day, levels = rnadays)
rna2UMAP<-umap(rna2PCA[,1:(ncol(rna2PCA)-2)], custom.config)
rna2UMAP<-as.data.frame(rna2UMAP$layout)
rna2UMAP$Day<-factor(rna2d$Day, levels = rnadays)
rna2UMAP$Diet<-rna2d$Diet
colnames(rna2UMAP)[1:2]<-c('UMAP1','UMAP2')
rna2UMAPplot<-ggplot(rna2UMAP, aes(x=UMAP1, y=UMAP2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(1,2.5))+geom_hline(yintercept = 0, size=0.24)+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+scale_fill_manual(values = as.vector(colour_code$Diet))+ggtitle("UMAP plot for RNA-seq data")
rec2rnaUMAPplot+rna2UMAPplot+plot_annotation(tag_levels = 'A')

4.3 Discovery of differentially expressed genes (DEGs):

We identify DEGs for day 7 (since this is the final day when the mice are fed separate diets, and RNA-seq is also available for this day). We define DEGs as genes identified to be significantly differentially expressed using a threshold (padj < 0.05) by both DESeq2 and edgeR (multiple testing, since we have 3 groups).

rec2.deseq<-recoRdseq.DE(rec2_day7, rec2d_day7, tool='DESeq2')
rec2.edger<-recoRdseq.DE(rec2_day7, rec2d_day7, tool='edgeR')
rec2.deseq.genes<-recoRdseq.filterDEG(rec2.deseq, p = 0.05)
rec2.edger.genes<-recoRdseq.filterDEG(rec2.edger, p = 0.05)
rec2.DEG<-rec2.deseq[intersect(rec2.deseq.genes, rec2.edger.genes), c(1,7, which(grepl('log2FoldChange', colnames(rec2.deseq))))]
rec2.DEG$geneID<-as.character(rec2.DEG$geneID)
rec2.DEG<-rec2.DEG[order(rec2.DEG$padj),]
ribosomal<-c(grep("rrs", rownames(rec2.DEG)), grep("rrl", rownames(rec2.DEG)))
if(length(ribosomal)>0){
  rec2.DEG<-rec2.DEG[-ribosomal,]
}
rna2.deseq<-recoRdseq.DE(rna2_day7, rna2d_day7, tool='DESeq2')
rna2.edger<-recoRdseq.DE(rna2_day7, rna2d_day7, tool='edgeR')
rna2.deseq.genes<-recoRdseq.filterDEG(rna2.deseq, p = 0.05)
rna2.edger.genes<-recoRdseq.filterDEG(rna2.edger, p = 0.05)
rna2.DEG<-rna2.deseq[intersect(rna2.deseq.genes, rna2.edger.genes), c(1,7, which(grepl('log2FoldChange', colnames(rna2.deseq))))]
rna2.DEG$geneID<-as.character(rna2.DEG$geneID)
rna2.DEG<-rna2.DEG[order(rna2.DEG$padj),]
ribosomal<-c(grep("rrs", rownames(rna2.DEG)), grep("rrl", rownames(rna2.DEG)))
if(length(ribosomal)>0){
  rna2.DEG<-rna2.DEG[-ribosomal,]
}
rec2.novel<-rec2.DEG[-which(rec2.DEG$geneID%in%rna2.DEG$geneID),]

We also look for DE genes over days 2-7 in Record-seq using a looser confidence threshold (padj <0.1) to identify consistent diet-signature genes.

rec2.DEG.list<-list()
rec2.er<-list()
rec2.de<-list()
rec2.global.DEG<-c()
for(i in unique(rec2d$Day)){
  if(i<8){
    dt<-rec2d[which(rec2d$Day==i), 1, drop=FALSE]
    dt$Diet<-factor(dt$Diet)
    de<-rec2[,which(colnames(rec2)%in%rownames(dt))]
    rec2.de[[i]]<-recoRdseq.DE(de,dt,tool='DESeq2')
    rec2.er[[i]]<-recoRdseq.DE(de,dt,tool='edgeR')
    rec2.de.genes<-recoRdseq.filterDEG(rec2.de[[i]], p = 0.1)
    rec2.er.genes<-recoRdseq.filterDEG(rec2.er[[i]], p = 0.1)
    rec2.DEG.list[[i]]<-rec2.de[[i]][intersect(rec2.de.genes, rec2.er.genes), c(1,7, which(grepl('log2FoldChange', colnames(rec2.de[[i]]))))]
    rec2.global.DEG<- c(rec2.global.DEG, as.character(intersect(rec2.de.genes,rec2.er.genes)))
  }
}
rec2.global.DEG1<-as.data.frame(table(rec2.global.DEG)[order(table(rec2.global.DEG), decreasing = TRUE)])
rec2.global.DEG2<-as.data.frame(table(rec2.global.DEG)[order(table(rec2.global.DEG), decreasing = TRUE)])
colnames(rec2.global.DEG1)<-c("geneID", "days_DE")
colnames(rec2.global.DEG2)<-c("geneID", "days_DE")
rec2.global.DEG1$geneID<-as.character(rec2.global.DEG1$geneID)
rec2.global.DEG2$geneID<-as.character(rec2.global.DEG2$geneID)
for(i in unique(rec2d$Day)){
  if(i<8){
  rec2.global.DEG1$V1<-rec2.de[[i]][rec2.global.DEG1$geneID,4]
  colnames(rec2.global.DEG1)[ncol(rec2.global.DEG1)]<-paste0("log2FoldChange_FC_day", i)
  rec2.global.DEG2$V1<-rec2.de[[i]][rec2.global.DEG2$geneID,8]
  colnames(rec2.global.DEG2)[ncol(rec2.global.DEG2)]<-paste0("log2FoldChange_SC_day", i)

  }
}
rec2.global.DEG1$log2FoldChange.max<-rec2.global.DEG1[,3:ncol(rec2.global.DEG1)][cbind(1:nrow(rec2.global.DEG1[,3:ncol(rec2.global.DEG1)]), max.col(replace(x <- abs(rec2.global.DEG1[,3:ncol(rec2.global.DEG1)]), is.na(x), -Inf)))]
rec2.global.DEG2$log2FoldChange.max<-rec2.global.DEG2[,3:ncol(rec2.global.DEG2)][cbind(1:nrow(rec2.global.DEG2[,3:ncol(rec2.global.DEG2)]), max.col(replace(x <- abs(rec2.global.DEG2[,3:ncol(rec2.global.DEG2)]), is.na(x), -Inf)))]

rec2.global.DEG<-rec2.global.DEG1[,c(1,2,ncol(rec2.global.DEG1))]
rec2.global.DEG$V1<-rec2.global.DEG2[,ncol(rec2.global.DEG2)]
colnames(rec2.global.DEG)[3]<-"log2FoldChange.max_FC"
colnames(rec2.global.DEG)[4]<-"log2FoldChange.max_SC"

4.4 Plotting individual DEGs:

We plot vst-transformed genome-aligning spacer counts for 6 genes in the gntR pathway for chow and starch fed mice on day 7.

gntR_genes<-c('eda','edd', 'gntT', 'kdgT', 'gntU', 'gntK')
de<-rec2d_day7[rec2d_day7$Diet!='Fat',]
dt<-rec2_day7tf[gntR_genes, rownames(de)]
rec2.gntR.plot.df<-data.frame(Diet=de$Diet, t(dt))
rec2.gntR.plot.df<-melt(rec2.gntR.plot.df, id.vars = 'Diet')
rec2.gntR.plot<-ggplot(rec2.gntR.plot.df, aes(y=value, x=variable, fill=Diet, color='black'))+geom_boxplot(size=0.24, outlier.size=0)+geom_point(size=0.48, position = position_dodge(0.75))+theme_pub+ylab("gene-aligning spacer counts (vst-transformed)")+xlab("gene")+ggtitle("Record-seq counts on day 7 for gntR gene")+scale_fill_manual(values = as.vector(colour_code$Diet[c(1,3)]))+scale_color_manual(values = c("black"), guide='none')
rec2.gntR.plot+plot_annotation()

4.5 Heatmap for Record-seq and RNA-seq DEGs on day 7

We plot heatmaps showing hierarchical clustering of samples using detected DE genes for both Record-seq and RNA-seq on day 7.

cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
dheatmap<-as.data.frame(t(apply(rec2_day7tf[rec2.DEG$geneID,], 1, zscorestandardize)))
heatmap.rec2.day7<-pheatmap(dheatmap, annotation_col = rec2d_day7[,1, drop=FALSE], annotation_colors=colour_code, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = TRUE, treeheight_row = 0,  treeheight_col = 5, show_colnames = FALSE, show_rownames = FALSE, color = cols,fontsize_number=5, width=2.28, height=2.28, main='Record-seq DEGs day 7')

heatmap.genelist<-heatmap.rec2.day7$tree_row$labels[heatmap.rec2.day7$tree_row$order]
heatmap.genelist<-rec2_day7tf[heatmap.genelist,]
colnames(heatmap.genelist)<-as.character(rec2d_day7$Diet)

cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
dheatmap<-as.data.frame(t(apply(rna2_day7tf[rna2.DEG$geneID,], 1, zscorestandardize)))
heatmap.rna2.day7<-pheatmap(dheatmap, annotation_col = rna2d_day7[,1, drop=FALSE], annotation_colors=colour_code, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = TRUE, treeheight_row = 0, treeheight_col = 5, show_colnames = FALSE,show_rownames = FALSE, color = cols,fontsize_number=5, width=2.28, height=2.28, main='RNA-seq DEGs day 7')

4.6 Volcano plots and heatmaps for Record-seq and RNA-seq DEGs:

We perform pairwise DE analysis using DESeq2 to identify log2FC and p-adj values for each diet pair on day 7, and plot volcanoes (log2FC>1.5, padj<0.1)

Record-seq:

levels<-sort(unique(rec2d_day7[,1]))
pairwise.combo<-combn(levels, 2)
color.combo<-combn(colour_code$Diet, 2)
rec2.de.vals<-list()
rec2.ed.vals<-list()
vol.plots<-list()
DEG<-list()
for(i in 1:dim(pairwise.combo)[2]){
  ds<-rec2d_day7[which(rec2d_day7[,1]%in%pairwise.combo[,i]),]
  ds$Diet<-as.character(ds$Diet)
  dt<-rec2_day7[,rownames(ds)]
  dtf<-recoRdseq.transform(dt,ds)
  rec2.de.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'DESeq2')
  rec2.ed.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'edgeR')
  rownames(rec2.de.vals[[i]]) <- rec2.de.vals[[i]]$geneID
  rownames(rec2.ed.vals[[i]]) <- rec2.ed.vals[[i]]$geneID
  deseq.genes<-recoRdseq.filterDEG(rec2.de.vals[[i]], p = 0.1)
  edger.genes<-recoRdseq.filterDEG(rec2.ed.vals[[i]], p = 0.1)
  DEG[[i]]<-data.frame(row.names=intersect(deseq.genes, edger.genes),geneID=intersect(deseq.genes, edger.genes),log2Foldchange=rec2.de.vals[[i]][intersect(deseq.genes, edger.genes), 4], padj=rec2.de.vals[[i]][intersect(deseq.genes, edger.genes), 7])
  ribosomal<-c(grep("rrs", rownames(DEG[[i]])), grep("rrl", rownames(DEG[[i]])))
  if(length(ribosomal)>0){
      DEG[[i]]<-DEG[[i]][-ribosomal,]
  }
  DEG[[i]]$geneID<-as.character(DEG[[i]]$geneID)
  rec2.de.vals[[i]]<-rec2.de.vals[[i]][complete.cases(rec2.de.vals[[i]]),]
  rec2.de.vals[[i]]$Group<-'None'
  rec2.de.vals[[i]]$Group[ which(rec2.de.vals[[i]]$log2FoldChange>1.5&rec2.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[2]))
  rec2.de.vals[[i]]$Group[ which(rec2.de.vals[[i]]$log2FoldChange<(-1.5)&rec2.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", sort(unique(ds$Diet))[1])
 rec2.de.vals[[i]]$Group<-factor(rec2.de.vals[[i]]$Group, levels = c(paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[1])), paste0("upregulated.in.", sort(unique(ds$Diet))[2]), 'None' ))
  rec2.de.vals[[i]]$label<-FALSE
  m1<-rec2.de.vals[[i]][rec2.de.vals[[i]]$log2FoldChange>1.5, 'geneID'][1:10]
  m2<-rec2.de.vals[[i]][rec2.de.vals[[i]]$log2FoldChange<(-1.5), 'geneID'][1:10]
  m<-which(rec2.de.vals[[i]]$geneID%in%union(m1,m2))
  for(j in m){
    if(abs(rec2.de.vals[[i]]$log2FoldChange[j])>1.5&rec2.de.vals[[i]]$padj[j]<0.1){
      rec2.de.vals[[i]]$label[j]<-TRUE
    }
  }
  vol.plots[[i]]<-ggplot(rec2.de.vals[[i]], aes( x=log2FoldChange, y=(-log10(padj)), color=Group))+scale_colour_manual(values = c(color.combo[,i], 'gray70'))+geom_point(size=0.24)+geom_text_repel(data = rec2.de.vals[[i]][which(rec2.de.vals[[i]]$label),], aes( x=log2FoldChange, y=(-log10(padj)), label=geneID), size=1.76, show.legend=FALSE)+theme_pub+geom_vline(xintercept = 1.5, size=0.24)+geom_vline(xintercept = -1.5, size=0.24)+geom_hline(yintercept = 1, size=0.24)+xlab("log2 fold change")+ylab("-log10 p-adjusted value")+guides(color = guide_legend(override.aes = list(size=1.5)))
}

vol.plots[[1]] + vol.plots[[2]] +vol.plots[[3]] + plot_annotation(tag_levels = 'A')+plot_layout(ncol = 2)

RNA-seq:

levels<-sort(unique(rna2d_day7[,1]))
pairwise.combo<-combn(levels, 2)
color.combo<-combn(colour_code$Diet, 2)
rna2.de.vals<-list()
rna2.ed.vals<-list()
vol.plots<-list()
DEG<-list()
for(i in 1:dim(pairwise.combo)[2]){
  ds<-rna2d_day7[which(rna2d_day7[,1]%in%pairwise.combo[,i]),]
  ds$Diet<-as.character(ds$Diet)
  dt<-rna2_day7[,rownames(ds)]
  dtf<-recoRdseq.transform(dt,ds)
  rna2.de.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'DESeq2')
  rna2.ed.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'edgeR')
  rownames(rna2.de.vals[[i]]) <- rna2.de.vals[[i]]$geneID
  rownames(rna2.ed.vals[[i]]) <- rna2.ed.vals[[i]]$geneID
  deseq.genes<-recoRdseq.filterDEG(rna2.de.vals[[i]], p = 0.1)
  edger.genes<-recoRdseq.filterDEG(rna2.ed.vals[[i]], p = 0.1)
  DEG[[i]]<-data.frame(row.names=intersect(deseq.genes, edger.genes),geneID=intersect(deseq.genes, edger.genes),log2Foldchange=rna2.de.vals[[i]][intersect(deseq.genes, edger.genes), 4], padj=rna2.de.vals[[i]][intersect(deseq.genes, edger.genes), 7])
  ribosomal<-c(grep("rrs", rownames(DEG[[i]])), grep("rrl", rownames(DEG[[i]])))
  if(length(ribosomal)>0){
      DEG[[i]]<-DEG[[i]][-ribosomal,]
  }
  DEG[[i]]$geneID<-as.character(DEG[[i]]$geneID)
  rna2.de.vals[[i]]<-rna2.de.vals[[i]][complete.cases(rna2.de.vals[[i]]),]
  rna2.de.vals[[i]]$Group<-'None'
  rna2.de.vals[[i]]$Group[ which(rna2.de.vals[[i]]$log2FoldChange>1.5&rna2.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[2]))
  rna2.de.vals[[i]]$Group[ which(rna2.de.vals[[i]]$log2FoldChange<(-1.5)&rna2.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", sort(unique(ds$Diet))[1])
 rna2.de.vals[[i]]$Group<-factor(rna2.de.vals[[i]]$Group, levels = c(paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[1])), paste0("upregulated.in.", sort(unique(ds$Diet))[2]), 'None' ))
  rna2.de.vals[[i]]$label<-FALSE
  m1<-rna2.de.vals[[i]][rna2.de.vals[[i]]$log2FoldChange>1.5, 'geneID'][1:10]
  m2<-rna2.de.vals[[i]][rna2.de.vals[[i]]$log2FoldChange<(-1.5), 'geneID'][1:10]
  m<-which(rna2.de.vals[[i]]$geneID%in%union(m1,m2))
  for(j in m){
    if(abs(rna2.de.vals[[i]]$log2FoldChange[j])>1.5&rna2.de.vals[[i]]$padj[j]<0.1){
      rna2.de.vals[[i]]$label[j]<-TRUE
    }
  }
  vol.plots[[i]]<-ggplot(rna2.de.vals[[i]], aes( x=log2FoldChange, y=(-log10(padj)), color=Group))+scale_colour_manual(values = c(color.combo[,i], 'gray70'))+geom_point(size=0.24)+geom_text_repel(data = rna2.de.vals[[i]][which(rna2.de.vals[[i]]$label),], aes( x=log2FoldChange, y=(-log10(padj)), label=geneID), size=1.76, show.legend=FALSE)+theme_pub+geom_vline(xintercept = 1.5, size=0.24)+geom_vline(xintercept = -1.5, size=0.24)+geom_hline(yintercept = 1, size=0.24)+xlab("log2 fold change")+ylab("-log10 p-adjusted value")+guides(color = guide_legend(override.aes = list(size=1.5)))
}

vol.plots[[1]] + vol.plots[[2]] +vol.plots[[3]] + plot_annotation(tag_levels = 'A')+plot_layout(ncol = 2)

NA
NA
NA

4.7 Hierarchical clustering on final day using DEGs

We want to check whether information about diet groups prior to switch can be retrieved at day 20 - i.e 13 days after the switch. For this, we use diet signature genes identified before the switch (DEGs) to hierarchically cluster the groups. We can almost perfectly classify groups using Record-seq data, while for RNA-seq, the groups converge.

rec2d_day20<-rec2d[rec2d$Day==20,]
rec2_day20<-rec2[,rownames(rec2d_day20)]
rec2_day20_tf<-recoRdseq.transform(rec2_day20, rec2d_day20)

cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
ribosomal<-c(grep("rrs", rec2.global.DEG$geneID), grep("rrl", rec2.global.DEG$geneID))
if(length(ribosomal)>0){
  rec2.global.DEG<-rec2.global.DEG[-ribosomal,]
}
rec2.geneShortList<-unique(c(rec2.global.DEG[which(rec2.global.DEG$log2FoldChange.max_FC>2.5), 1][1:10], rec2.global.DEG[which(rec2.global.DEG$log2FoldChange.max_SC>2.5), 1][1:10],rec2.global.DEG[which(rec2.global.DEG$log2FoldChange.max_SC<(-2.5)&rec2.global.DEG$log2FoldChange.max_FC<(-2.5)), 1][1:10]))
dheatmap<-as.data.frame(t(apply(rec2_day20_tf[rec2.geneShortList,], 1, zscorestandardize)))
heatmap.rec2<-pheatmap(dheatmap, annotation_col = rec2d_day20[,1, drop=FALSE], annotation_colors=colour_code, treeheight_row=0, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = FALSE, show_colnames = FALSE,color = cols,fontsize_number=5, main='Hierarchical clustering of Record-seq data on day 20 based on diet signature genes')

heatmap.genelist<-rec2_day20_tf[rec2.geneShortList,]
colnames(heatmap.genelist)<-as.character(rec2d_day20$Diet)

rna2d_day20<-rna2d[rna2d$Day==20,]
rna2_day20<-rna2[,rownames(rna2d_day20)]
rna2_day20_tf<-recoRdseq.transform(rna2_day20, rna2d_day20)
rna2.geneShortList<-unique(c(rna2.DEG[rna2.DEG$log2FoldChange.Fat_vs_Chow>2.5, 1][1:10], rna2.DEG[rna2.DEG$log2FoldChange.Starch_vs_Chow>2.5, 1][1:10], rna2.DEG[which(rowMeans(rna2.DEG[,3:4])<(-2.5)), 1][1:10]))
cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
dheatmap<-as.data.frame(t(apply(rna2_day20_tf[rna2.geneShortList,], 1, zscorestandardize)))
heatmap.rna2<-pheatmap(dheatmap, annotation_col = rna2d_day20[,1, drop=FALSE], annotation_colors=colour_code, treeheight_row=0, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = FALSE, show_colnames = FALSE, color = cols,fontsize_number=5, main='Hierarchical clustering of RNA-seq data on day 20 based on diet signature genes')

heatmap.genelist<-rna2_day20_tf[rna2.geneShortList,]
colnames(heatmap.genelist)<-as.character(rna2d_day20$Diet)

4.8 Ecocyc analysis:

We create enrichment plots for top differentially regulated pathways identified by Ecocyc using the pairwise DEG lists generated here.

ChowXStarch.pathway<-as.data.frame(read.table("data/Chow.Starch.pathway.txt", header=TRUE, sep = '\t'))
ChowXStarch.pathway$p.values[ChowXStarch.pathway$group=='enriched.in.Chow']<-log10(ChowXStarch.pathway$p.values[ChowXStarch.pathway$group=='enriched.in.Chow'])*(-1)
ChowXStarch.pathway$p.values[ChowXStarch.pathway$group=='enriched.in.Starch']<-log10(ChowXStarch.pathway$p.values[ChowXStarch.pathway$group=='enriched.in.Starch'])
ChowXStarch.pathway<-ChowXStarch.pathway[order(ChowXStarch.pathway$p.values),]
ChowXStarch.pathway.plot<-ggplot(ChowXStarch.pathway, aes(x=Pathway, y=p.values, size=number.of.genes, colour=group ))+geom_point()+coord_flip()+theme_pub+scale_size_continuous(range = c(1,3))+geom_hline(yintercept = (-1.30103), size=0.24)+geom_hline(yintercept = 0, size=0.24)+geom_hline(yintercept = 1.30103, size=0.24)+xlab("")+ylab("-log10 p-adjusted value")+ labs(size = "Genes detected")+scale_x_discrete(limits=ChowXStarch.pathway$Pathway)+scale_color_manual(values = c(as.character(colour_code$Diet[c(1,3)])))

ChowXStarch.pathway.plot+plot_annotation()


ChowXFat.pathway<-as.data.frame(read.table("data/Chow.Fat.pathway.txt", header=TRUE, sep = '\t'))
ChowXFat.pathway$p.values[ChowXFat.pathway$group=='enriched.in.Chow']<-log10(ChowXFat.pathway$p.values[ChowXFat.pathway$group=='enriched.in.Chow'])*(-1)
ChowXFat.pathway$p.values[ChowXFat.pathway$group=='enriched.in.Fat']<-log10(ChowXFat.pathway$p.values[ChowXFat.pathway$group=='enriched.in.Fat'])
ChowXFat.pathway<-ChowXFat.pathway[order(ChowXFat.pathway$p.values),]
ChowXFat.pathway.plot<-ggplot(ChowXFat.pathway, aes(x=Pathway, y=p.values, size=number.of.genes, colour=group ))+geom_point()+coord_flip()+theme_pub+scale_size_continuous(range = c(1,3))+geom_hline(yintercept = (-1.30103), size=0.24)+geom_hline(yintercept = 0, size=0.24)+geom_hline(yintercept = 1.30103, size=0.24)+xlab("")+ylab("-log10 p-adjusted value")+ labs(size = "Genes detected")+scale_x_discrete(limits=ChowXFat.pathway$Pathway)+scale_color_manual(values = c(as.character(colour_code$Diet[c(1,2)])))

ChowXFat.pathway.plot+plot_annotation()


FatXStarch.pathway<-as.data.frame(read.table("data/Fat.Starch.pathway.txt", header=TRUE, sep = '\t'))
FatXStarch.pathway$p.values[FatXStarch.pathway$group=='enriched.in.Fat']<-log10(FatXStarch.pathway$p.values[FatXStarch.pathway$group=='enriched.in.Fat'])*(-1)
FatXStarch.pathway$p.values[FatXStarch.pathway$group=='enriched.in.Starch']<-log10(FatXStarch.pathway$p.values[FatXStarch.pathway$group=='enriched.in.Starch'])
FatXStarch.pathway<-FatXStarch.pathway[order(FatXStarch.pathway$p.values),]
FatXStarch.pathway.plot<-ggplot(FatXStarch.pathway, aes(x=Pathway, y=p.values, size=number.of.genes, colour=group ))+geom_point()+coord_flip()+theme_pub+scale_size_continuous(range = c(1,3))+geom_hline(yintercept = (-1.30103), size=0.24)+geom_hline(yintercept = 0, size=0.24)+geom_hline(yintercept = 1.30103, size=0.24)+xlab("")+ylab("-log10 p-adjusted value")+ labs(size = "Genes detected")+scale_x_discrete(limits=FatXStarch.pathway$Pathway)+scale_color_manual(values = c(as.character(colour_code$Diet[c(2,3)])))

FatXStarch.pathway.plot+plot_annotation()

NA
NA

5 Checking reproducibility of classifier genes (DEGs)

We want to confirm that there is an overlap among the DEGs identified in the two experimental replicates, and the direction of differential regulation is consistent. We use the genes that are upregulated/downregulated in the Chow group compared to the Starch group on day 7.

deseq.genes<-recoRdseq.filterDEG(rec1.de.vals[[2]], p = 0.1)
edger.genes<-recoRdseq.filterDEG(rec1.ed.vals[[2]], p = 0.1)
rec1.Chow.v.Starch.DEG<-data.frame(row.names = intersect(deseq.genes, edger.genes),geneID=intersect(deseq.genes, edger.genes), log2FC=rec1.de.vals[[2]][intersect(deseq.genes, edger.genes),4])

deseq.genes<-recoRdseq.filterDEG(rec2.de.vals[[2]], p = 0.1)
edger.genes<-recoRdseq.filterDEG(rec2.ed.vals[[2]], p = 0.1)
rec2.Chow.v.Starch.DEG<-data.frame(row.names = intersect(deseq.genes, edger.genes),geneID=intersect(deseq.genes, edger.genes), log2FC=rec2.de.vals[[2]][intersect(deseq.genes, edger.genes),4])

plot(euler(list(rec1 = rec1.Chow.v.Starch.DEG$geneID, rec2 = rec2.Chow.v.Starch.DEG$geneID)) , quantities=TRUE)

Finally, we plot a correlation plot based on the log2FC detected for DEGs for the two experiments, and estimate the number of DEGs regulated in a similar direction.

 DEG.compare<-data.frame(geneID=intersect(rec1.Chow.v.Starch.DEG$geneID, rec2.Chow.v.Starch.DEG$geneID))
DEG.compare$geneID<-as.character(DEG.compare$geneID)
DEG.compare$rec1_log2FC<-rec1.Chow.v.Starch.DEG[DEG.compare$geneID,2]
DEG.compare$rec2_log2FC<-rec2.Chow.v.Starch.DEG[DEG.compare$geneID,2]
DEG.compare<-DEG.compare[complete.cases(DEG.compare), ]
r2<-round(cor(DEG.compare$rec1_log2FC, DEG.compare$rec2_log2FC)^2,2)
n<-round(length(which(DEG.compare$rec1_log2FC*DEG.compare$rec2_log2FC>0))*100/length(DEG.compare$geneID))
DEG.scatterplot<-ggplot(DEG.compare, aes(y=rec1_log2FC, x=rec2_log2FC))+geom_point(size=0.48, aes(colour='gray10'))+geom_smooth(method = 'lm', se = FALSE, size=0.48)+theme_pub+geom_hline(yintercept = 0, size=0.24)+geom_vline(xintercept = 0, size=0.24)+xlab("log2 fold change of DEGs detected in transient diet 2")+ylab("corresponding log2 fold change in transient diet 1")+annotate("text",  x=Inf, y = Inf, label = paste0("R^2 =",as.character(r2), " \n DEGs regulated in the same direction = ",as.character(n), "%"), vjust=1, hjust=1, size=3)+scale_color_manual(values = c('gray10'), guide='none')+ggtitle("Correlation of overlapping DEGs detected in both experiments")
DEG.scatterplot+plot_annotation()

6 Information about R session

sessionInfo()
R version 4.0.3 (2020-10-10)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS  12.5.1

Matrix products: default
LAPACK: /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
 [1] parallel  stats4    grid      stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] ggrepel_0.9.1               patchwork_1.1.1             factoextra_1.0.7            recoRdseq_0.2              
 [5] umap_0.2.8.0                RColorBrewer_1.1-3          gplots_3.1.3                reshape2_1.4.4             
 [9] baySeq_2.24.0               abind_1.4-5                 edgeR_3.32.1                DESeq2_1.30.1              
[13] SummarizedExperiment_1.20.0 Biobase_2.50.0              MatrixGenerics_1.2.1        matrixStats_0.62.0         
[17] GenomicRanges_1.42.0        GenomeInfoDb_1.26.7         IRanges_2.24.1              S4Vectors_0.28.1           
[21] BiocGenerics_0.36.1         cluster_2.1.3               ggfortify_0.4.14            pheatmap_1.0.12            
[25] VennDiagram_1.7.3           futile.logger_1.4.3         plyr_1.8.7                  eulerr_6.1.1               
[29] devtools_2.4.3              usethis_2.1.5               forcats_0.5.1               stringr_1.4.0              
[33] dplyr_1.0.9                 purrr_0.3.4                 readr_2.1.2                 tidyr_1.2.0                
[37] tibble_3.1.7                tidyverse_1.3.1             ggplot2_3.3.6               randtests_1.0.1            
[41] limma_3.46.0                readxl_1.4.0               

loaded via a namespace (and not attached):
  [1] backports_1.4.1        systemfonts_1.0.4      polylabelr_0.2.0       splines_4.0.3          BiocParallel_1.24.1   
  [6] digest_0.6.29          htmltools_0.5.2        fansi_1.0.3            magrittr_2.0.3         memoise_2.0.1         
 [11] tzdb_0.3.0             remotes_2.4.2          annotate_1.68.0        modelr_0.1.8           bdsmatrix_1.3-4       
 [16] askpass_1.1            prettyunits_1.1.1      colorspace_2.0-3       apeglm_1.12.0          blob_1.2.3            
 [21] rvest_1.0.2            textshaping_0.3.6      haven_2.5.0            xfun_0.30              callr_3.7.0           
 [26] crayon_1.5.1           RCurl_1.98-1.6         jsonlite_1.8.0         genefilter_1.72.1      survival_3.3-1        
 [31] glue_1.6.2             polyclip_1.10-0        gtable_0.3.0           zlibbioc_1.36.0        XVector_0.30.0        
 [36] DelayedArray_0.16.3    pkgbuild_1.3.1         scales_1.2.0           mvtnorm_1.1-3          futile.options_1.0.1  
 [41] DBI_1.1.2              Rcpp_1.0.8.3           emdbook_1.3.12         xtable_1.8-4           reticulate_1.25       
 [46] bit_4.0.4              httr_1.4.3             ellipsis_0.3.2         pkgconfig_2.0.3        XML_3.99-0.9          
 [51] farver_2.1.0           dbplyr_2.1.1           locfit_1.5-9.4         utf8_1.2.2             tidyselect_1.1.2      
 [56] labeling_0.4.2         rlang_1.0.2            AnnotationDbi_1.52.0   munsell_0.5.0          cellranger_1.1.0      
 [61] tools_4.0.3            cachem_1.0.6           cli_3.3.0              generics_0.1.2         RSQLite_2.2.11        
 [66] broom_0.8.0            evaluate_0.15          fastmap_1.1.0          yaml_2.3.5             ragg_1.2.2            
 [71] processx_3.5.3         knitr_1.39             bit64_4.0.5            fs_1.5.2               caTools_1.18.2        
 [76] nlme_3.1-157           formatR_1.12           xml2_1.3.3             brio_1.1.3             compiler_4.0.3        
 [81] rstudioapi_0.13        png_0.1-7              testthat_3.1.4         reprex_2.0.1           geneplotter_1.68.0    
 [86] stringi_1.7.6          ps_1.7.0               RSpectra_0.16-0        desc_1.4.1             lattice_0.20-45       
 [91] Matrix_1.4-1           vctrs_0.4.1            pillar_1.7.0           lifecycle_1.0.1        bitops_1.0-7          
 [96] R6_2.5.1               KernSmooth_2.23-20     gridExtra_2.3          sessioninfo_1.2.2      lambda.r_1.2.4        
[101] MASS_7.3-56            gtools_3.9.2           assertthat_0.2.1       pkgload_1.2.4          openssl_2.0.0         
[106] rprojroot_2.0.3        withr_2.5.0            GenomeInfoDbData_1.2.4 mgcv_1.8-40            hms_1.1.1             
[111] coda_0.19-4            rmarkdown_2.14         bbmle_1.0.24           numDeriv_2016.8-1.1    lubridate_1.8.0       
LS0tCnRpdGxlOiAiVHJhbnNpZW50IERpZXRzIEkgKyBJSSBhbmFseXNpcyAtIFJOQS1zZXEgJiBSZWNvcmQtc2VxIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMgogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKLS0tCiMgSW50cm9kdWN0aW9uIAoKVGhpcyBSIG5vdGVib29rIGltcGxlbWVudHMgdGhlIGFuYWx5c2lzIG9mIHRoZSBSZWNvcmQtc2VxIGFuZCBSTkEtc2VxIHJlYWRvdXQgb2YgdGhlIHRyYW5zaWVudCBkaWV0IGV4cGVyaW1lbnRzIGRlc2NyaWJlZCBpbiAnTm9uaW52YXNpdmUgYXNzZXNzbWVudCBvZiBndXQgZnVuY3Rpb24gdXNpbmcgdHJhbnNjcmlwdGlvbmFsIHJlY29yZGluZyBzZW50aW5lbCBjZWxscycgbWFudXNjcmlwdC4gVGhlIGZvbGxvd2luZyBmaWxlcyBhcmUgc3RvcmVkIGluIHRoZSBgZGF0YWAgZGlyZWN0b3J5IHdpdGhpbiB0aGUgd29ya2luZyBkaXJlY3Rvcnk6IAoKICAgIHRyYW5zaWVudC1kaWV0LwogICAgICAgIHNlY29uZGFyeUFuYWx5c2lzLlJtZAogICAgICAgIGRhdGEvCiAgICAgICAgICAgIHRyYW5zaWVudERpZXQxX1JlY29yZHNlcV9nZW5vbWVhbGlnbmluZy50eHQKICAgICAgICAgICAgdHJhbnNpZW50RGlldDFfUmVjb3Jkc2VxX21ldGFkYXRhLnR4dAogICAgICAgICAgICB0cmFuc2llbnREaWV0MV9STkFzZXFfZ2Vub21lYWxpZ25pbmcudHh0CiAgICAgICAgICAgIHRyYW5zaWVudERpZXQxX1JOQXNlcV9tZXRhZGF0YS50eHQKICAgICAgICAgICAgdHJhbnNpZW50RGlldDFfZGF5MTRfUk5Bc2VxX2dlbm9tZWFsaWduaW5nLnR4dAogICAgICAgICAgICB0cmFuc2llbnREaWV0MV9kYXkxNF9STkFzZXFfbWV0YWRhdGEudHh0ICAKICAgICAgICAgICAgdHJhbnNpZW50RGlldDJfUmVjb3Jkc2VxX2dlbm9tZWFsaWduaW5nLnR4dAogICAgICAgICAgICB0cmFuc2llbnREaWV0Ml9SZWNvcmRzZXFfbWV0YWRhdGEudHh0CiAgICAgICAgICAgIHRyYW5zaWVudERpZXQyX1JOQXNlcV9nZW5vbWVhbGlnbmluZy50eHQKICAgICAgICAgICAgdHJhbnNpZW50RGlldDJfUk5Bc2VxX21ldGFkYXRhLnR4dAogICAgICAgICAgICBDaG93LkZhdC5wYXRod2F5LnR4dAogICAgICAgICAgICBDaG93LlN0YXJjaC5wYXRod2F5LnR4dAogICAgICAgICAgICBGYXQuU3RhcmNoLnBhdGh3YXkudHh0CiAgICAgICAgCiMgTGlicmFyaWVzCgpUaGUgYHJlY29SZHNlcWAgcGFja2FnZSBhbmQgZGVwZW5kZW5jaWVzIGFyZSByZXF1aXJlZCBmb3IgdGhpcyBhbmFseXNpcywgYW5kIHRoZSBmYW50YXN0aWMgYHBhdGNod29ya2AgcGFja2FnZSBpcyB1c2VkIGZvciB2aXN1YWxpemF0aW9uLiAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmlmKCFyZXF1aXJlKGRldnRvb2xzKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiZGV2dG9vbHMiKQp9CmxpYnJhcnkoZGV2dG9vbHMpCmlmKCFyZXF1aXJlKGV1bGVycikpewogIGluc3RhbGwucGFja2FnZXMoImV1bGVyciIpCn0KbGlicmFyeShldWxlcnIpCmlmKCFyZXF1aXJlKHBseXIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJwbHlyIikKfQpsaWJyYXJ5KHBseXIpCmlmKCFyZXF1aXJlKHJlY29SZHNlcSkpewogIGluc3RhbGxfZ2l0aHViKCJwbGF0dGxhYi9UcmFuc2NyaXB0aW9uYWwtUmVjb3JkaW5nIiwgc3ViZGlyPSJyZWNvUmRzZXEiKQp9CmlmKCFyZXF1aXJlKGZhY3RvZXh0cmEpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJmYWN0b2V4dHJhIikKfQpsaWJyYXJ5KGZhY3RvZXh0cmEpCmlmKCFyZXF1aXJlKHBhdGNod29yaykpewogIGluc3RhbGwucGFja2FnZXMoJ3BhdGNod29yaycpCn0KaWYoIXJlcXVpcmUoZ2dyZXBlbCkpewogIGluc3RhbGwucGFja2FnZXMoJ2dncmVwZWwnKQp9CmxpYnJhcnkocmVjb1Jkc2VxKQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KGdncmVwZWwpCmNvbG91cl9jb2RlID0gbGlzdCgKICBEaWV0ID0gYyhDaG93ID0gIiM1MzhiY2UiLCBGYXQ9IiNlZDkxNWMiLCBTdGFyY2g9JyM0MmJiN2YnKSkgIyB3ZSBzZXQgYSBjb25zaXN0ZW50IGNvbG9yIHNjaGVtZSBmb3IgdGhlIHRocmVlIGRpZXQgZ3JvdXBzCgp0aGVtZV9wdWI8LXRoZW1lX21pbmltYWwoKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIsIGxlZ2VuZC5qdXN0aWZpY2F0aW9uPSJjZW50ZXIiLCBsZWdlbmQubWFyZ2luPW1hcmdpbigwLDAsMCwwKSxsZWdlbmQuYm94Lm1hcmdpbj1tYXJnaW4oLTEwLC0xMCwtMTAsLTEwKSxwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwgbGVnZW5kLnNwYWNpbmcueSA9ICB1bml0KDAsICdtbScpLCBsZWdlbmQuYm94PSd2ZXJ0aWNhbCcsIGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoMC4xLCAiY20iKSxsZWdlbmQua2V5LndpZHRoID0gdW5pdCgwLjEsImNtIiksIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTUpLCB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9NSksIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTUsIGNvbG91cj0nYmxhY2snKSwgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShzaXplID0gMC4yNCwgY29sb3VyPSdncmF5MScsIGxpbmV0eXBlID0gMikpICMgd2Ugc2V0IGEgY29uc2lzdGVudCB0aGVtZSBmb3IgZ2dwbG90IG9iamVjdHMKCmN1c3RvbS5jb25maWcgPSB1bWFwLmRlZmF1bHRzCmN1c3RvbS5jb25maWckcmFuZG9tX3N0YXRlID0gMgpgYGAKIyBUcmFuc2llbnQgRGlldCAxCgpEYXRhIGZvciB0aGUgdHJhbnNpZW50IGRpZXQgZXhwZXJpbWVudCB3aXRoIDE0IGRheXMgaXMgYW5hbHl6ZWQgZmlyc3QuCgojIyBJbXBvcnRpbmcgYW5kIHByZS1wcm9jZXNzaW5nIGRhdGEgZm9yIHRyYW5zaWVudCBkaWV0IDEKCiAgV2UgaW1wb3J0IHRoZSBkYXRhIG1hdHJpY2VzIGZvciBib3RoIFJlY29yZC1zZXEgYW5kIFJOQS1zZXEsIGZpbHRlciB0aGVtIGZvciBsb3dseSBleHByZXNzZWQgZ2VuZXMgYXMgd2VsbCBhcyBvdXRsaWVyIHNhbXBsZXMgd2l0aCBsb3cgY3VtdWxhdGl2ZSBjb3VudHMsIGFuZCB1c2UgdnN0IGZyb20gX0RFU2VxMl8gdG8gbm9ybWFsaXplIGFuZCB0cmFuc2Zvcm0gdGhlIGRhdGEuIFdlIHVzZSBhIHRocmVzaG9sZCBvZiAxMGsgY291bnRzIGZvciBleGNsdWRpbmcgUmVjb3JkLXNlcSBzYW1wbGVzIGFuZCAxMDBrIGNvdW50cyBmb3IgZXhjbHVkaW5nIFJOQS1zZXEgc2FtcGxlcy4gCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpyZWMxPC1hcy5kYXRhLmZyYW1lKHJlYWQudGFibGUoImRhdGEvdHJhbnNpZW50RGlldDFfUmVjb3Jkc2VxX2dlbm9tZWFsaWduaW5nLnR4dCIsIGhlYWRlciA9IFRSVUUpKQpyZWMxZDwtYXMuZGF0YS5mcmFtZShyZWFkLnRhYmxlKCJkYXRhL3RyYW5zaWVudERpZXQxX1JlY29yZHNlcV9tZXRhZGF0YS50eHQiLCBoZWFkZXIgPSBUUlVFKSkKREVMaXN0PC1yZWNvUmRzZXEucHJlcHJvY2VzcyhyZWMxLCByZWMxZCwgbWluQ291bnRzUGVyU2FtcGxlID0gMTAwMDApCnJlYzE8LURFTGlzdFtbMV1dCnJlYzFkPC1ERUxpc3RbWzJdXQpyZWMxZDwtcmVjMWRbcmVjMWQkRGF5PjEsXQpyZWMxPC1yZWMxWyxyb3duYW1lcyhyZWMxZCldCnJlYzFfdGY8LXJlY29SZHNlcS50cmFuc2Zvcm0ocmVjMSwgcmVjMWQsdHJhbnNmb3JtYXRpb24gPSAndnN0JykKCnJuYTE8LWFzLmRhdGEuZnJhbWUocmVhZC50YWJsZSgiZGF0YS90cmFuc2llbnREaWV0MV9STkFzZXFfZ2Vub21lYWxpZ25pbmcudHh0IiwgaGVhZGVyID0gVFJVRSkpCnJuYTFkPC1hcy5kYXRhLmZyYW1lKHJlYWQudGFibGUoImRhdGEvdHJhbnNpZW50RGlldDFfUk5Bc2VxX21ldGFkYXRhLnR4dCIsIGhlYWRlciA9IFRSVUUpKQpERUxpc3Q8LXJlY29SZHNlcS5wcmVwcm9jZXNzKHJuYTEsIHJuYTFkLCBtaW5Db3VudHNQZXJTYW1wbGUgPSAxMDAwMDApCnJuYTE8LURFTGlzdFtbMV1dCnJuYTFkPC1ERUxpc3RbWzJdXQpybmExX3RmPC1yZWNvUmRzZXEudHJhbnNmb3JtKHJuYTEsIHJuYTFkKQpybmFkYXlzPC11bmlxdWUocm5hMWQkRGF5KQpybmExX2RheTE0PC1hcy5kYXRhLmZyYW1lKHJlYWQudGFibGUoImRhdGEvdHJhbnNpZW50RGlldDFfZGF5MTRfUk5Bc2VxX2dlbm9tZWFsaWduaW5nLnR4dCIsIGhlYWRlciA9IFRSVUUpKQpybmExZF9kYXkxNDwtYXMuZGF0YS5mcmFtZShyZWFkLnRhYmxlKCJkYXRhL3RyYW5zaWVudERpZXQxX2RheTE0X1JOQXNlcV9tZXRhZGF0YS50eHQiLCBoZWFkZXIgPSBUUlVFKSkKcm5hMWRfZGF5MTQ8LXJuYTFkX2RheTE0WywxOjNdCkRFTGlzdDwtcmVjb1Jkc2VxLnByZXByb2Nlc3Mocm5hMV9kYXkxNCwgcm5hMWRfZGF5MTQsIG1pbkNvdW50c1BlclNhbXBsZSA9IDEwMDAwMCkKcm5hMV9kYXkxNDwtREVMaXN0W1sxXV0Kcm5hMWRfZGF5MTQ8LURFTGlzdFtbMl1dCnJuYTFfZGF5MTR0ZjwtcmVjb1Jkc2VxLnRyYW5zZm9ybShybmExX2RheTE0LCBybmExZF9kYXkxNCwgdHJhbnNmb3JtYXRpb24gPSAndnN0JykKCmBgYAoKIyMgRGF0YSBleHBsb3JhdGlvbgogIFdlIHVzZSBQcmluY2lwYWwgQ29tcG9uZW50IGFuYWx5c2lzIGFuZCBVTUFQIGZvciBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gYW5kIGV4cGxvcmluZyBjbHVzdGVycyBpbiBhbiB1bnN1cGVydmlzZWQgZmFzaGlvbiBpbiBvdXIgZGF0YS4gIFdlIGZpcnN0IGdlbmVyYXRlIHRoZXNlIGZvciB0aGUgZW50aXJlIGRhdGFzZXQgZnJvbSBSZWNvcmQtc2VxOgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgIGZpZy5oZWlnaHQ9Mi4yLCBmaWcud2lkdGg9M30KcmVjMXNkcyA8LSByb3dTZHMoYXMubWF0cml4KHJlYzFfdGYpKQpvIDwtIG9yZGVyKHJlYzFzZHMsIGRlY3JlYXNpbmcgPSBUUlVFKQpyZWMxUENBPC1wcmNvbXAodChyZWMxX3RmW29bMTo1MDBdLF0pKQpwY2Ffc3RhdDwtc3VtbWFyeShyZWMxUENBKQpwY2FfdmFyaWFuY2U8LXBjYV9zdGF0JGltcG9ydGFuY2VbMixdCnJlYzFQQ0E8LWFzLmRhdGEuZnJhbWUocmVjMVBDQSR4KQpyZWMxUENBJERpZXQ8LXJlYzFkJERpZXQKcmVjMVBDQSREYXk8LWZhY3RvcihyZWMxZCREYXksIGxldmVscyA9IDE6MjApCnJlYzFQQ0FwbG90PC1nZ3Bsb3QocmVjMVBDQSwgYWVzKHg9UEMxLCB5PVBDMiwgZmlsbD1EaWV0LCAgc2l6ZT1EYXkpKStnZW9tX3BvaW50KHBjaD0yMSwgY29sb3VyPScjMDAwMDAwJywgc3Ryb2tlPTAuMjUpK3RoZW1lX3B1YitzY2FsZV9zaXplX2Rpc2NyZXRlKHJhbmdlPWMoMSwyLjUpKStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGFzLnZlY3Rvcihjb2xvdXJfY29kZSREaWV0KSkrZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KSsgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTIpKSkreWxhYihwYXN0ZTAoIlBDMiAoIiwgYXMuY2hhcmFjdGVyKHBjYV92YXJpYW5jZVsyXSoxMDApLCAiJSB2YXJpYW5jZSBleHBsYWluZWQpIikpK3hsYWIocGFzdGUwKCJQQzEgKCIsIGFzLmNoYXJhY3RlcihwY2FfdmFyaWFuY2VbMV0qMTAwKSwgIiUgdmFyaWFuY2UgZXhwbGFpbmVkKSIpKStnZ3RpdGxlKCIgUENBIHBsb3Qgb2YgUmVjb3JkLXNlcSBkYXRhIC0gYWxsIGRheXMgKHRyYW5zaWVudCBkaWV0IDEpIikKCgpyZWMxVU1BUDwtdW1hcChyZWMxUENBWywxOihuY29sKHJlYzFQQ0EpLTIpXSwgY3VzdG9tLmNvbmZpZykKcmVjMVVNQVA8LWFzLmRhdGEuZnJhbWUocmVjMVVNQVAkbGF5b3V0KQpyZWMxVU1BUCREYXk8LWZhY3RvcihyZWMxZCREYXksIGxldmVscyA9IDI6MTQpCnJlYzFVTUFQJERpZXQ8LXJlYzFkJERpZXQKY29sbmFtZXMocmVjMVVNQVApWzE6Ml08LWMoJ1VNQVAxJywnVU1BUDInKQpyZWMxVU1BUHBsb3Q8LWdncGxvdChyZWMxVU1BUCwgYWVzKHg9VU1BUDEsIHk9VU1BUDIsIGZpbGw9RGlldCwgIHNpemU9RGF5KSkrZ2VvbV9wb2ludChwY2g9MjEsIGNvbG91cj0nIzAwMDAwMCcsIHN0cm9rZT0wLjI0KSt0aGVtZV9wdWIrc2NhbGVfc2l6ZV9kaXNjcmV0ZShyYW5nZT1jKDEsMi41KSkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpKyBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MikpKStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBhcy52ZWN0b3IoY29sb3VyX2NvZGUkRGlldCkpK2dndGl0bGUoIlVNQVAgcGxvdCBmb3IgUmVjb3JkLXNlcSBkYXRhIC0gYWxsIGRheXMgKHRyYW5zaWVudCBkaWV0IDEpIikKCnJlYzFQQ0FwbG90K3JlYzFVTUFQcGxvdCtwbG90X2Fubm90YXRpb24odGFnX2xldmVscyA9ICdBJykKCmBgYAoKICBGb3IgYSBjb21wYXJpc29uIGJldHdlZW4gUmVjb3JkLXNlcSBhbmQgUk5BLXNlcSwgd2UgY2hlY2sgaWYgdGhlIGRpZXQgZ3JvdXBzIGNhbiBiZSBjbGFzc2lmaWVkIHVzaW5nIFBDQSBvbiBkYXkgNyAodGhlIGxhc3QgZGF5IHdoZW4gdGhlIG1pY2UgYXJlIGZlZCBkaWZmZXJlbnQgZGlldHMsIGJlZm9yZSBzd2l0Y2hpbmcgYWxsIG1pY2UgdG8gYSAnQ2hvdycgZGlldCkKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi4yLCBmaWcud2lkdGg9M30KCnJlYzFkX2RheTc8LXJlYzFkW3JlYzFkJERheT09NyxdCnJlYzFfZGF5NzwtcmVjMVssIHJvd25hbWVzKHJlYzFkX2RheTcpXQpyZWMxX2RheTd0ZjwtcmVjb1Jkc2VxLnRyYW5zZm9ybShyZWMxX2RheTcsIHJlYzFkX2RheTcpCnJlYzFfZGF5N19zZHMgPC0gcm93U2RzKGFzLm1hdHJpeChyZWMxX2RheTd0ZikpCm8gPC0gb3JkZXIocmVjMV9kYXk3X3NkcywgZGVjcmVhc2luZyA9IFRSVUUpCnJlYzFfZGF5N1BDQTwtcHJjb21wKHQocmVjMV9kYXk3dGZbb1sxOjUwMF0sXSkpCnBjYV9zdGF0PC1zdW1tYXJ5KHJlYzFfZGF5N1BDQSkKcGNhX3ZhcmlhbmNlPC1wY2Ffc3RhdCRpbXBvcnRhbmNlWzIsXQpyZWMxX2RheTdQQ0E8LWFzLmRhdGEuZnJhbWUocmVjMV9kYXk3UENBJHgpCnJlYzFfZGF5N1BDQSREaWV0PC1yZWMxZF9kYXk3JERpZXQKcmVjMV9kYXk3UENBJERheTwtZmFjdG9yKHJlYzFkX2RheTckRGF5LCBsZXZlbHMgPSBjKDcpKQpyZWMxX2RheTdQQ0FwbG90PC1nZ3Bsb3QocmVjMV9kYXk3UENBLCBhZXMoeD1QQzEsIHk9UEMyLCBmaWxsPURpZXQsICBzaXplPURheSkpK2dlb21fcG9pbnQocGNoPTIxLCBjb2xvdXI9JyMwMDAwMDAnLCBzdHJva2U9MC4yNSkrdGhlbWVfcHViK3NjYWxlX3NpemVfZGlzY3JldGUocmFuZ2U9YygyLDIuNSkpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXQpKStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpKyBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MikpKSt5bGFiKHBhc3RlMCgiUEMyICgiLCBhcy5jaGFyYWN0ZXIocGNhX3ZhcmlhbmNlWzJdKjEwMCksICIlIHZhcmlhbmNlIGV4cGxhaW5lZCkiKSkreGxhYihwYXN0ZTAoIlBDMSAoIiwgYXMuY2hhcmFjdGVyKHBjYV92YXJpYW5jZVsxXSoxMDApLCAiJSB2YXJpYW5jZSBleHBsYWluZWQpIikpK2dndGl0bGUoIiBQQ0EgcGxvdCBvZiBSZWNvcmQtc2VxIGRhdGEgb24gZGF5IDcgKHRyYW5zaWVudCBkaWV0IDEpIikKCnJuYTFfc2RzIDwtIHJvd1Nkcyhhcy5tYXRyaXgocm5hMV90ZikpCm8gPC0gb3JkZXIocm5hMV9zZHMsIGRlY3JlYXNpbmcgPSBUUlVFKQpybmExX1BDQTwtcHJjb21wKHQocm5hMV90ZltvWzE6NTAwXSxdKSkKcGNhX3N0YXQ8LXN1bW1hcnkocm5hMV9QQ0EpCnBjYV92YXJpYW5jZTwtcGNhX3N0YXQkaW1wb3J0YW5jZVsyLF0Kcm5hMV9QQ0E8LWFzLmRhdGEuZnJhbWUocm5hMV9QQ0EkeCkKcm5hMV9QQ0EkRGlldDwtcm5hMWQkRGlldApybmExX1BDQSREYXk8LWZhY3RvcihybmExZCREYXksIGxldmVscyA9IGMoNykpCnJuYTFfZGF5N1BDQXBsb3Q8LWdncGxvdChybmExX1BDQSwgYWVzKHg9UEMxLCB5PVBDMiwgZmlsbD1EaWV0LCAgc2l6ZT1EYXkpKStnZW9tX3BvaW50KHBjaD0yMSwgY29sb3VyPScjMDAwMDAwJywgc3Ryb2tlPTAuMjUpK3RoZW1lX3B1YitzY2FsZV9zaXplX2Rpc2NyZXRlKHJhbmdlPWMoMiwyLjUpKStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGFzLnZlY3Rvcihjb2xvdXJfY29kZSREaWV0KSkrZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KSsgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTIpKSkreWxhYihwYXN0ZTAoIlBDMiAoIiwgYXMuY2hhcmFjdGVyKHBjYV92YXJpYW5jZVsyXSoxMDApLCAiJSB2YXJpYW5jZSBleHBsYWluZWQpIikpK3hsYWIocGFzdGUwKCJQQzEgKCIsIGFzLmNoYXJhY3RlcihwY2FfdmFyaWFuY2VbMV0qMTAwKSwgIiUgdmFyaWFuY2UgZXhwbGFpbmVkKSIpKStnZ3RpdGxlKCIgUENBIHBsb3Qgb2YgUk5BLXNlcSBkYXRhIG9uIGRheSA3ICh0cmFuc2llbnQgZGlldCAxKSAiKQoKCnJuYTFfZGF5N1BDQXBsb3QrcmVjMV9kYXk3UENBcGxvdCtwbG90X2Fubm90YXRpb24odGFnX2xldmVscyA9ICdBJykKCmBgYAoKICBOZXh0LCB3ZSBjaGVjayBpZiBSZWNvcmQtc2VxIG9yIFJOQS1zZXEgY2FuIGRpc3Rpbmd1aXNoIHRoZSBzYW1wbGVzIG9uIGRheSAxNCAoNyBkYXlzIGFmdGVyIHN3aXRjaGluZyBhbGwgc2FtcGxlcyB0byBDaG93IGRpZXQpLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjIsIGZpZy53aWR0aD0zfQoKcmVjMWRfZGF5MTQ8LXJlYzFkW3JlYzFkJERheT09MTQsXQpyZWMxX2RheTE0PC1yZWMxWywgcm93bmFtZXMocmVjMWRfZGF5MTQpXQpyZWMxX2RheTE0dGY8LXJlY29SZHNlcS50cmFuc2Zvcm0ocmVjMV9kYXkxNCwgcmVjMWRfZGF5MTQpCnJlYzFfZGF5MTRfc2RzIDwtIHJvd1Nkcyhhcy5tYXRyaXgocmVjMV9kYXkxNHRmKSkKbyA8LSBvcmRlcihyZWMxX2RheTE0X3NkcywgZGVjcmVhc2luZyA9IFRSVUUpCnJlYzFfZGF5MTRQQ0E8LXByY29tcCh0KHJlYzFfZGF5MTR0ZltvWzE6NTAwXSxdKSkKcGNhX3N0YXQ8LXN1bW1hcnkocmVjMV9kYXkxNFBDQSkKcGNhX3ZhcmlhbmNlPC1wY2Ffc3RhdCRpbXBvcnRhbmNlWzIsXQpyZWMxX2RheTE0UENBPC1hcy5kYXRhLmZyYW1lKHJlYzFfZGF5MTRQQ0EkeCkKcmVjMV9kYXkxNFBDQSREaWV0PC1yZWMxZF9kYXkxNCREaWV0CnJlYzFfZGF5MTRQQ0EkRGF5PC1mYWN0b3IocmVjMWRfZGF5MTQkRGF5LCBsZXZlbHMgPSBjKDE0KSkKcmVjMV9kYXkxNFBDQXBsb3Q8LWdncGxvdChyZWMxX2RheTE0UENBLCBhZXMoeD1QQzEsIHk9UEMyLCBmaWxsPURpZXQsICBzaXplPURheSkpK2dlb21fcG9pbnQocGNoPTIxLCBjb2xvdXI9JyMwMDAwMDAnLCBzdHJva2U9MC4yNSkrdGhlbWVfcHViK3NjYWxlX3NpemVfZGlzY3JldGUocmFuZ2U9YygyLDIuNSkpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXQpKStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpKyBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MikpKSt5bGFiKHBhc3RlMCgiUEMyICgiLCBhcy5jaGFyYWN0ZXIocGNhX3ZhcmlhbmNlWzJdKjEwMCksICIlIHZhcmlhbmNlIGV4cGxhaW5lZCkiKSkreGxhYihwYXN0ZTAoIlBDMSAoIiwgYXMuY2hhcmFjdGVyKHBjYV92YXJpYW5jZVsxXSoxMDApLCAiJSB2YXJpYW5jZSBleHBsYWluZWQpIikpK2dndGl0bGUoIiBQQ0EgcGxvdCBvZiBSZWNvcmQtc2VxIGRhdGEgb24gZGF5IDE0ICh0cmFuc2llbnQgZGlldCAxKSIpCgpybmExX2RheTE0X3NkcyA8LSByb3dTZHMoYXMubWF0cml4KHJuYTFfZGF5MTR0ZikpCm8gPC0gb3JkZXIocm5hMV9kYXkxNF9zZHMsIGRlY3JlYXNpbmcgPSBUUlVFKQpybmExX2RheTE0X1BDQTwtcHJjb21wKHQocm5hMV9kYXkxNHRmW29bMTo1MDBdLF0pKQpwY2Ffc3RhdDwtc3VtbWFyeShybmExX2RheTE0X1BDQSkKcGNhX3ZhcmlhbmNlPC1wY2Ffc3RhdCRpbXBvcnRhbmNlWzIsXQpybmExX2RheTE0X1BDQTwtYXMuZGF0YS5mcmFtZShybmExX2RheTE0X1BDQSR4KQpybmExX2RheTE0X1BDQSREaWV0PC1ybmExZF9kYXkxNCREaWV0CnJuYTFfZGF5MTRfUENBJERheTwtZmFjdG9yKHJuYTFkX2RheTE0JERheSwgbGV2ZWxzID0gYygxNCkpCnJuYTFfZGF5MTRQQ0FwbG90PC1nZ3Bsb3Qocm5hMV9kYXkxNF9QQ0EsIGFlcyh4PVBDMSwgeT1QQzIsIGZpbGw9RGlldCwgIHNpemU9RGF5KSkrZ2VvbV9wb2ludChwY2g9MjEsIGNvbG91cj0nIzAwMDAwMCcsIHN0cm9rZT0wLjI1KSt0aGVtZV9wdWIrc2NhbGVfc2l6ZV9kaXNjcmV0ZShyYW5nZT1jKDIsMi41KSkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBhcy52ZWN0b3IoY29sb3VyX2NvZGUkRGlldCkpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0yKSkpK3lsYWIocGFzdGUwKCJQQzIgKCIsIGFzLmNoYXJhY3RlcihwY2FfdmFyaWFuY2VbMl0qMTAwKSwgIiUgdmFyaWFuY2UgZXhwbGFpbmVkKSIpKSt4bGFiKHBhc3RlMCgiUEMxICgiLCBhcy5jaGFyYWN0ZXIocGNhX3ZhcmlhbmNlWzFdKjEwMCksICIlIHZhcmlhbmNlIGV4cGxhaW5lZCkiKSkrZ2d0aXRsZSgiIFBDQSBwbG90IG9mIFJOQS1zZXEgZGF0YSBvbiBkYXkgMTQgKHRyYW5zaWVudCBkaWV0IDEpICIpCgoKcm5hMV9kYXkxNFBDQXBsb3QrcmVjMV9kYXkxNFBDQXBsb3QrcGxvdF9hbm5vdGF0aW9uKHRhZ19sZXZlbHMgPSAnQScpCgpgYGAKCgojIyBEaXNjb3Zlcnkgb2YgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzIChERUdzKToKICBXZSBpZGVudGlmeSBERUdzIGZvciBkYXkgNyAoc2luY2UgdGhpcyBpcyB0aGUgZmluYWwgZGF5IHdoZW4gdGhlIG1pY2UgYXJlIGZlZCBzZXBhcmF0ZSBkaWV0cywgYW5kIFJOQS1zZXEgaXMgYWxzbyBhdmFpbGFibGUgZm9yIHRoaXMgZGF5KS4gV2UgZGVmaW5lIERFR3MgYXMgZ2VuZXMgaWRlbnRpZmllZCB0byBiZSBzaWduaWZpY2FudGx5IGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCB1c2luZyBhIHRocmVzaG9sZCAocGFkaiA8IDAuMDUpIGJ5IGJvdGggREVTZXEyIGFuZCBlZGdlUiAobXVsdGlwbGUgdGVzdGluZywgc2luY2Ugd2UgaGF2ZSAzIGdyb3VwcykuIAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcmVjMS5kZXNlcTwtcmVjb1Jkc2VxLkRFKHJlYzFfZGF5NywgcmVjMWRfZGF5NywgdG9vbD0nREVTZXEyJykKcmVjMS5lZGdlcjwtcmVjb1Jkc2VxLkRFKHJlYzFfZGF5NywgcmVjMWRfZGF5NywgdG9vbD0nZWRnZVInKQpyZWMxLmRlc2VxLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzEuZGVzZXEsIHAgPSAwLjA1KQpyZWMxLmVkZ2VyLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzEuZWRnZXIsIHAgPSAwLjA1KQpyZWMxLkRFRzwtcmVjMS5kZXNlcVtpbnRlcnNlY3QocmVjMS5kZXNlcS5nZW5lcywgcmVjMS5lZGdlci5nZW5lcyksIGMoMSw3LCB3aGljaChncmVwbCgnbG9nMkZvbGRDaGFuZ2UnLCBjb2xuYW1lcyhyZWMxLmRlc2VxKSkpKV0KcmVjMS5ERUckZ2VuZUlEPC1hcy5jaGFyYWN0ZXIocmVjMS5ERUckZ2VuZUlEKQpyZWMxLkRFRzwtcmVjMS5ERUdbb3JkZXIocmVjMS5ERUckcGFkaiksXQpyZWMxLkRFRyRnZW5lSUQ8LWFzLmNoYXJhY3RlcihyZWMxLkRFRyRnZW5lSUQpCnJuYTEuZGVzZXE8LXJlY29SZHNlcS5ERShybmExLCBybmExZCwgdG9vbD0nREVTZXEyJykKcm5hMS5lZGdlcjwtcmVjb1Jkc2VxLkRFKHJuYTEsIHJuYTFkLCB0b29sPSdlZGdlUicpCnJuYTEuZGVzZXEuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocm5hMS5kZXNlcSwgcCA9IDAuMDUpCnJuYTEuZWRnZXIuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocm5hMS5lZGdlciwgcCA9IDAuMDUpCnJuYTEuREVHPC1ybmExLmRlc2VxW2ludGVyc2VjdChybmExLmRlc2VxLmdlbmVzLCBybmExLmVkZ2VyLmdlbmVzKSwgYygxLDcsIHdoaWNoKGdyZXBsKCdsb2cyRm9sZENoYW5nZScsIGNvbG5hbWVzKHJuYTEuZGVzZXEpKSkpXQpybmExLkRFRyRnZW5lSUQ8LWFzLmNoYXJhY3RlcihybmExLkRFRyRnZW5lSUQpCgpyZWMxLm5vdmVsPC1yZWMxLkRFR1std2hpY2gocmVjMS5ERUckZ2VuZUlEJWluJXJuYTEuREVHJGdlbmVJRCksXQpgYGAKICBGb3IgUmVjb3JkLXNlcSwgd2UgYWxzbyBsb29rIGZvciBERSBnZW5lcyBvdmVyIGRheXMgMi03IGluIFJlY29yZC1zZXEgdXNpbmcgYSBsb29zZXIgY29uZmlkZW5jZSB0aHJlc2hvbGQgKHBhZGogPDAuMSkgdG8gaWRlbnRpZnkgY29uc2lzdGVudCBkaWV0LXNpZ25hdHVyZSBnZW5lcy4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnJlYzEuREVHLmxpc3Q8LWxpc3QoKQpyZWMxLmVyPC1saXN0KCkKcmVjMS5kZTwtbGlzdCgpCnJlYzEuZ2xvYmFsLkRFRzwtYygpCmZvcihpIGluIHVuaXF1ZShyZWMxZCREYXkpKXsKICBpZihpPDgpewogICAgZHQ8LXJlYzFkW3doaWNoKHJlYzFkJERheT09aSksIDEsIGRyb3A9RkFMU0VdCiAgICBkZTwtcmVjMVssd2hpY2goY29sbmFtZXMocmVjMSklaW4lcm93bmFtZXMoZHQpKV0KICAgIHJlYzEuZGVbW2ldXTwtcmVjb1Jkc2VxLkRFKGRlLGR0LHRvb2w9J0RFU2VxMicpCiAgICByZWMxLmVyW1tpXV08LXJlY29SZHNlcS5ERShkZSxkdCx0b29sPSdlZGdlUicpCiAgICByZWMxLmRlLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzEuZGVbW2ldXSwgcCA9IDAuMSkKICAgIHJlYzEuZXIuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMS5lcltbaV1dLCBwID0gMC4xKQogICAgcmVjMS5ERUcubGlzdFtbaV1dPC1yZWMxLmRlW1tpXV1baW50ZXJzZWN0KHJlYzEuZGUuZ2VuZXMsIHJlYzEuZXIuZ2VuZXMpLCBjKDEsNywgd2hpY2goZ3JlcGwoJ2xvZzJGb2xkQ2hhbmdlJywgY29sbmFtZXMocmVjMS5kZVtbaV1dKSkpKV0KICAgIHJlYzEuZ2xvYmFsLkRFRzwtIGMocmVjMS5nbG9iYWwuREVHLCBhcy5jaGFyYWN0ZXIoaW50ZXJzZWN0KHJlYzEuZGUuZ2VuZXMscmVjMS5lci5nZW5lcykpKQogIH0KfQpyZWMxLmdsb2JhbC5ERUcxPC1hcy5kYXRhLmZyYW1lKHRhYmxlKHJlYzEuZ2xvYmFsLkRFRylbb3JkZXIodGFibGUocmVjMS5nbG9iYWwuREVHKSwgZGVjcmVhc2luZyA9IFRSVUUpXSkKcmVjMS5nbG9iYWwuREVHMjwtYXMuZGF0YS5mcmFtZSh0YWJsZShyZWMxLmdsb2JhbC5ERUcpW29yZGVyKHRhYmxlKHJlYzEuZ2xvYmFsLkRFRyksIGRlY3JlYXNpbmcgPSBUUlVFKV0pCmNvbG5hbWVzKHJlYzEuZ2xvYmFsLkRFRzEpPC1jKCJnZW5lSUQiLCAiZGF5c19ERSIpCmNvbG5hbWVzKHJlYzEuZ2xvYmFsLkRFRzIpPC1jKCJnZW5lSUQiLCAiZGF5c19ERSIpCnJlYzEuZ2xvYmFsLkRFRzEkZ2VuZUlEPC1hcy5jaGFyYWN0ZXIocmVjMS5nbG9iYWwuREVHMSRnZW5lSUQpCnJlYzEuZ2xvYmFsLkRFRzIkZ2VuZUlEPC1hcy5jaGFyYWN0ZXIocmVjMS5nbG9iYWwuREVHMiRnZW5lSUQpCmZvcihpIGluIHVuaXF1ZShyZWMxZCREYXkpKXsKICBpZihpPDgpewogIHJlYzEuZ2xvYmFsLkRFRzEkVjE8LXJlYzEuZGVbW2ldXVtyZWMxLmdsb2JhbC5ERUcxJGdlbmVJRCw0XQogIGNvbG5hbWVzKHJlYzEuZ2xvYmFsLkRFRzEpW25jb2wocmVjMS5nbG9iYWwuREVHMSldPC1wYXN0ZTAoImxvZzJGb2xkQ2hhbmdlX0ZDX2RheSIsIGkpCiAgcmVjMS5nbG9iYWwuREVHMiRWMTwtcmVjMS5kZVtbaV1dW3JlYzEuZ2xvYmFsLkRFRzIkZ2VuZUlELDhdCiAgY29sbmFtZXMocmVjMS5nbG9iYWwuREVHMilbbmNvbChyZWMxLmdsb2JhbC5ERUcyKV08LXBhc3RlMCgibG9nMkZvbGRDaGFuZ2VfU0NfZGF5IiwgaSkKCiAgfQp9CnJlYzEuZ2xvYmFsLkRFRzEkbG9nMkZvbGRDaGFuZ2UubWF4PC1yZWMxLmdsb2JhbC5ERUcxWywzOm5jb2wocmVjMS5nbG9iYWwuREVHMSldW2NiaW5kKDE6bnJvdyhyZWMxLmdsb2JhbC5ERUcxWywzOm5jb2wocmVjMS5nbG9iYWwuREVHMSldKSwgbWF4LmNvbChyZXBsYWNlKHggPC0gYWJzKHJlYzEuZ2xvYmFsLkRFRzFbLDM6bmNvbChyZWMxLmdsb2JhbC5ERUcxKV0pLCBpcy5uYSh4KSwgLUluZikpKV0KcmVjMS5nbG9iYWwuREVHMiRsb2cyRm9sZENoYW5nZS5tYXg8LXJlYzEuZ2xvYmFsLkRFRzJbLDM6bmNvbChyZWMxLmdsb2JhbC5ERUcyKV1bY2JpbmQoMTpucm93KHJlYzEuZ2xvYmFsLkRFRzJbLDM6bmNvbChyZWMxLmdsb2JhbC5ERUcyKV0pLCBtYXguY29sKHJlcGxhY2UoeCA8LSBhYnMocmVjMS5nbG9iYWwuREVHMlssMzpuY29sKHJlYzEuZ2xvYmFsLkRFRzIpXSksIGlzLm5hKHgpLCAtSW5mKSkpXQoKcmVjMS5nbG9iYWwuREVHPC1yZWMxLmdsb2JhbC5ERUcxWyxjKDEsMixuY29sKHJlYzEuZ2xvYmFsLkRFRzEpKV0KcmVjMS5nbG9iYWwuREVHJFYxPC1yZWMxLmdsb2JhbC5ERUcyWyxuY29sKHJlYzEuZ2xvYmFsLkRFRzEpXQpjb2xuYW1lcyhyZWMxLmdsb2JhbC5ERUcpWzNdPC0ibG9nMkZvbGRDaGFuZ2UubWF4X0ZDIgpjb2xuYW1lcyhyZWMxLmdsb2JhbC5ERUcpWzRdPC0ibG9nMkZvbGRDaGFuZ2UubWF4X1NDIgoKYGBgCgoKIyMgUGxvdHRpbmcgaW5kaXZpZHVhbCBERUdzOgogIFdlIHBsb3QgdnN0LXRyYW5zZm9ybWVkIGdlbm9tZS1hbGlnbmluZyBzcGFjZXIgY291bnRzIGZvciA2IGdlbmVzIGluIHRoZSBnbnRSIHBhdGh3YXkgZm9yIGNob3cgYW5kIHN0YXJjaCBmZWQgbWljZSBvbiBkYXkgNy4KICAKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjIsIGZpZy53aWR0aD0zfQpnbnRSX2dlbmVzPC1jKCdlZGEnLCdlZGQnLCAnZ250VCcsICdrZGdUJywgJ2dudFUnLCAnZ250SycpCmRlPC1yZWMxZF9kYXk3W3JlYzFkX2RheTckRGlldCE9J0ZhdCcsXQpkdDwtcmVjMV9kYXk3dGZbZ250Ul9nZW5lcywgcm93bmFtZXMoZGUpXQpyZWMxLmdudFIucGxvdC5kZjwtZGF0YS5mcmFtZShEaWV0PWRlJERpZXQsIHQoZHQpKQpyZWMxLmdudFIucGxvdC5kZjwtbWVsdChyZWMxLmdudFIucGxvdC5kZiwgaWQudmFycyA9ICdEaWV0JykKcmVjMS5nbnRSLnBsb3Q8LWdncGxvdChyZWMxLmdudFIucGxvdC5kZiwgYWVzKHk9dmFsdWUsIHg9dmFyaWFibGUsIGZpbGw9RGlldCwgY29sb3I9J2JsYWNrJykpK2dlb21fYm94cGxvdChzaXplPTAuMjQsIG91dGxpZXIuc2l6ZT0wKStnZW9tX3BvaW50KHNpemU9MC40OCwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSgwLjc1KSkrdGhlbWVfcHViK3lsYWIoImdlbmUtYWxpZ25pbmcgc3BhY2VyIGNvdW50cyAodnN0LXRyYW5zZm9ybWVkKSIpK3hsYWIoImdlbmUiKStnZ3RpdGxlKCJSZWNvcmQtc2VxIGNvdW50cyBvbiBkYXkgNyBmb3IgZ250UiBnZW5lIikrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXRbYygxLDMpXSkpK3NjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJibGFjayIpLCBndWlkZT0nbm9uZScpCnJlYzEuZ250Ui5wbG90K3Bsb3RfYW5ub3RhdGlvbigpCgoKYGBgCiMjIEhlYXRtYXAgZm9yIFJlY29yZC1zZXEgYW5kIFJOQS1zZXEgREVHcyBvbiBkYXkgNwogIFdlIHBsb3QgaGVhdG1hcHMgc2hvd2luZyBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBvZiBzYW1wbGVzIHVzaW5nIGRldGVjdGVkIERFIGdlbmVzIGZvciBib3RoIFJlY29yZC1zZXEgYW5kIFJOQS1zZXEgb24gZGF5IDcuIAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjIsIGZpZy53aWR0aD0zfQpjb2xzPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJkb2RnZXJibHVlNCIsICJ3aGl0ZSIsInZpb2xldHJlZDQiKSkoMjU2KQpyaWJvc29tYWw8LWMoZ3JlcCgicnJzIiwgcm93bmFtZXMocmVjMS5ERUcpKSwgZ3JlcCgicnJsIiwgcm93bmFtZXMocmVjMS5ERUcpKSkKaWYobGVuZ3RoKHJpYm9zb21hbCk+MCl7CiAgcmVjMS5ERUc8LXJlYzEuREVHWy1yaWJvc29tYWwsXQp9CmRoZWF0bWFwPC1hcy5kYXRhLmZyYW1lKHQoYXBwbHkocmVjMV9kYXk3dGZbcmVjMS5ERUckZ2VuZUlELF0sIDEsIHpzY29yZXN0YW5kYXJkaXplKSkpCmhlYXRtYXAucmVjMS5kYXk3PC1waGVhdG1hcChkaGVhdG1hcCwgYW5ub3RhdGlvbl9jb2wgPSByZWMxZF9kYXk3WywxLCBkcm9wPUZBTFNFXSwgYW5ub3RhdGlvbl9jb2xvcnM9Y29sb3VyX2NvZGUsIGZvbnRzaXplID0gNSwgZm9udHNpemVfcm93ID0gNSwgZm9udHNpemVfY29sID0gNSwgY2x1c3Rlcl9yb3dzID0gVFJVRSwgdHJlZWhlaWdodF9yb3cgPSAwLCBjbHVzdGVyaW5nX2Rpc3RhbmNlX2NvbHMgPSAiY2FuYmVycmEiLCB0cmVlaGVpZ2h0X2NvbCA9IDUsc2hvd19jb2xuYW1lcyA9IEZBTFNFLCBzaG93X3Jvd25hbWVzID0gRkFMU0UsIGNvbG9yID0gY29scyxmb250c2l6ZV9udW1iZXI9NSwgd2lkdGg9Mi4yOCwgaGVpZ2h0PTIuMjgsIG1haW49J1JlY29yZC1zZXEgREVHcyBkYXkgNycpCnJpYm9zb21hbDwtYyhncmVwKCJycnMiLCByb3duYW1lcyhybmExLkRFRykpLCBncmVwKCJycmwiLCByb3duYW1lcyhybmExLkRFRykpKQppZihsZW5ndGgocmlib3NvbWFsKT4wKXsKICBybmExLkRFRzwtcm5hMS5ERUdbLXJpYm9zb21hbCxdCn0KY29sczwtIGNvbG9yUmFtcFBhbGV0dGUoYygiZG9kZ2VyYmx1ZTQiLCAid2hpdGUiLCJ2aW9sZXRyZWQ0IikpKDI1NikKZGhlYXRtYXA8LWFzLmRhdGEuZnJhbWUodChhcHBseShybmExX3RmW3JuYTEuREVHJGdlbmVJRCxdLCAxLCB6c2NvcmVzdGFuZGFyZGl6ZSkpKQpoZWF0bWFwLnJuYTEuZGF5NzwtcGhlYXRtYXAoZGhlYXRtYXAsIGFubm90YXRpb25fY29sID0gcm5hMWRbLDEsIGRyb3A9RkFMU0VdLCBhbm5vdGF0aW9uX2NvbG9ycz1jb2xvdXJfY29kZSwgZm9udHNpemUgPSA1LCBmb250c2l6ZV9yb3cgPSA1LCBmb250c2l6ZV9jb2wgPSA1LCBjbHVzdGVyX3Jvd3MgPSBUUlVFLCB0cmVlaGVpZ2h0X3JvdyA9IDAsIHRyZWVoZWlnaHRfY29sID0gNSwgc2hvd19jb2xuYW1lcyA9IEZBTFNFLHNob3dfcm93bmFtZXMgPSBGQUxTRSwgY29sb3IgPSBjb2xzLGZvbnRzaXplX251bWJlcj01LCB3aWR0aD0yLjI4LCBoZWlnaHQ9Mi4yOCwgbWFpbj0nUk5BLXNlcSBERUdzIGRheSA3JykKYGBgCgojIyBWb2xjYW5vIHBsb3RzIGZvciBSZWNvcmQtc2VxIERFR3MKCiAgV2UgcGVyZm9ybSBwYWlyd2lzZSBERSBhbmFseXNpcyB1c2luZyBERVNlcTIgYW5kIGVkZ2VSIHRvIGlkZW50aWZ5IGxvZzJGQyBhbmQgcC1hZGogdmFsdWVzIGZvciBlYWNoIGRpZXQgcGFpciBvbiBkYXkgNywgYW5kIHBsb3Qgdm9sY2Fub2VzIChsb2cyRkM+MS41LCBwYWRqPDAuMSkKCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuMiwgZmlnLndpZHRoPTN9CmxldmVsczwtc29ydCh1bmlxdWUocmVjMWRfZGF5N1ssMV0pKQpwYWlyd2lzZS5jb21ibzwtY29tYm4obGV2ZWxzLCAyKQpjb2xvci5jb21ibzwtY29tYm4oY29sb3VyX2NvZGUkRGlldCwgMikKcmVjMS5kZS52YWxzPC1saXN0KCkKcmVjMS5lZC52YWxzPC1saXN0KCkKdm9sLnBsb3RzPC1saXN0KCkKREVHPC1saXN0KCkKZm9yKGkgaW4gMTpkaW0ocGFpcndpc2UuY29tYm8pWzJdKXsKICBkczwtcmVjMWRfZGF5N1t3aGljaChyZWMxZF9kYXk3WywxXSVpbiVwYWlyd2lzZS5jb21ib1ssaV0pLF0KICBkcyREaWV0PC1hcy5jaGFyYWN0ZXIoZHMkRGlldCkKICBkdDwtcmVjMV9kYXk3Wyxyb3duYW1lcyhkcyldCiAgZHRmPC1yZWNvUmRzZXEudHJhbnNmb3JtKGR0LGRzKQogIHJlYzEuZGUudmFsc1tbaV1dIDwtIHJlY29SZHNlcS5ERShkdCwgZHMsIHRvb2wgPSAnREVTZXEyJykKICByZWMxLmVkLnZhbHNbW2ldXSA8LSByZWNvUmRzZXEuREUoZHQsIGRzLCB0b29sID0gJ2VkZ2VSJykKICByb3duYW1lcyhyZWMxLmRlLnZhbHNbW2ldXSkgPC0gcmVjMS5kZS52YWxzW1tpXV0kZ2VuZUlECiAgcm93bmFtZXMocmVjMS5lZC52YWxzW1tpXV0pIDwtIHJlYzEuZWQudmFsc1tbaV1dJGdlbmVJRAogIGRlc2VxLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzEuZGUudmFsc1tbaV1dLCBwID0gMC4xKQogIGVkZ2VyLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzEuZWQudmFsc1tbaV1dLCBwID0gMC4xKQogIERFR1tbaV1dPC1kYXRhLmZyYW1lKHJvdy5uYW1lcz1pbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSxnZW5lSUQ9aW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksbG9nMkZvbGRjaGFuZ2U9cmVjMS5kZS52YWxzW1tpXV1baW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksIDRdLCBwYWRqPXJlYzEuZGUudmFsc1tbaV1dW2ludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLCA3XSkKICByaWJvc29tYWw8LWMoZ3JlcCgicnJzIiwgcm93bmFtZXMoREVHW1tpXV0pKSwgZ3JlcCgicnJsIiwgcm93bmFtZXMoREVHW1tpXV0pKSkKICBpZihsZW5ndGgocmlib3NvbWFsKT4wKXsKICAgICAgREVHW1tpXV08LURFR1tbaV1dWy1yaWJvc29tYWwsXQogIH0KICBERUdbW2ldXSRnZW5lSUQ8LWFzLmNoYXJhY3RlcihERUdbW2ldXSRnZW5lSUQpCiAgcmVjMS5kZS52YWxzW1tpXV08LXJlYzEuZGUudmFsc1tbaV1dW2NvbXBsZXRlLmNhc2VzKHJlYzEuZGUudmFsc1tbaV1dKSxdCiAgcmVjMS5kZS52YWxzW1tpXV0kR3JvdXA8LSdOb25lJwogIHJlYzEuZGUudmFsc1tbaV1dJEdyb3VwWyB3aGljaChyZWMxLmRlLnZhbHNbW2ldXSRsb2cyRm9sZENoYW5nZT4xLjUmcmVjMS5kZS52YWxzW1tpXV0kcGFkajwwLjEpXTwtcGFzdGUwKCJ1cHJlZ3VsYXRlZC5pbi4iLCBhcy5jaGFyYWN0ZXIoc29ydCh1bmlxdWUoZHMkRGlldCkpWzJdKSkKICByZWMxLmRlLnZhbHNbW2ldXSRHcm91cFsgd2hpY2gocmVjMS5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2U8KC0xLjUpJnJlYzEuZGUudmFsc1tbaV1dJHBhZGo8MC4xKV08LXBhc3RlMCgidXByZWd1bGF0ZWQuaW4uIiwgc29ydCh1bmlxdWUoZHMkRGlldCkpWzFdKQogcmVjMS5kZS52YWxzW1tpXV0kR3JvdXA8LWZhY3RvcihyZWMxLmRlLnZhbHNbW2ldXSRHcm91cCwgbGV2ZWxzID0gYyhwYXN0ZTAoInVwcmVndWxhdGVkLmluLiIsIGFzLmNoYXJhY3Rlcihzb3J0KHVuaXF1ZShkcyREaWV0KSlbMV0pKSwgcGFzdGUwKCJ1cHJlZ3VsYXRlZC5pbi4iLCBzb3J0KHVuaXF1ZShkcyREaWV0KSlbMl0pLCAnTm9uZScgKSkKICByZWMxLmRlLnZhbHNbW2ldXSRsYWJlbDwtRkFMU0UKICBtMTwtcmVjMS5kZS52YWxzW1tpXV1bcmVjMS5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2U+MS41LCAnZ2VuZUlEJ11bMToxMF0KICBtMjwtcmVjMS5kZS52YWxzW1tpXV1bcmVjMS5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2U8KC0xLjUpLCAnZ2VuZUlEJ11bMToxMF0KICBtPC13aGljaChyZWMxLmRlLnZhbHNbW2ldXSRnZW5lSUQlaW4ldW5pb24obTEsbTIpKQogIGZvcihqIGluIG0pewogICAgaWYoYWJzKHJlYzEuZGUudmFsc1tbaV1dJGxvZzJGb2xkQ2hhbmdlW2pdKT4xLjUmcmVjMS5kZS52YWxzW1tpXV0kcGFkaltqXTwwLjEpewogICAgICByZWMxLmRlLnZhbHNbW2ldXSRsYWJlbFtqXTwtVFJVRQogICAgfQogIH0KICB2b2wucGxvdHNbW2ldXTwtZ2dwbG90KHJlYzEuZGUudmFsc1tbaV1dLCBhZXMoIHg9bG9nMkZvbGRDaGFuZ2UsIHk9KC1sb2cxMChwYWRqKSksIGNvbG9yPUdyb3VwKSkrc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKGNvbG9yLmNvbWJvWyxpXSwgJ2dyYXk3MCcpKStnZW9tX3BvaW50KHNpemU9MC4yNCkrZ2VvbV90ZXh0X3JlcGVsKGRhdGEgPSByZWMxLmRlLnZhbHNbW2ldXVt3aGljaChyZWMxLmRlLnZhbHNbW2ldXSRsYWJlbCksXSwgYWVzKCB4PWxvZzJGb2xkQ2hhbmdlLCB5PSgtbG9nMTAocGFkaikpLCBsYWJlbD1nZW5lSUQpLCBzaXplPTEuNzYsIHNob3cubGVnZW5kPUZBTFNFKSt0aGVtZV9wdWIrZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMS41LCBzaXplPTAuMjQpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IC0xLjUsIHNpemU9MC4yNCkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSwgc2l6ZT0wLjI0KSt4bGFiKCJsb2cyIGZvbGQgY2hhbmdlIikreWxhYigiLWxvZzEwIHAtYWRqdXN0ZWQgdmFsdWUiKStndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTEuNSkpKQp9Cgp2b2wucGxvdHNbWzFdXSArIHZvbC5wbG90c1tbMl1dICt2b2wucGxvdHNbWzNdXSArIHBsb3RfYW5ub3RhdGlvbih0YWdfbGV2ZWxzID0gJ0EnKStwbG90X2xheW91dChuY29sID0gMikKCmBgYAoKIyMgQ2x1c3RlcmluZyBvbiBmaW5hbCBkYXkgb2YgZXhwZXJpbWVudAogIFdlIHdhbnQgdG8gY2hlY2sgd2hldGhlciBpbmZvcm1hdGlvbiBhYm91dCBkaWV0IGdyb3VwcyBwcmlvciB0byBzd2l0Y2ggY2FuIGJlIHJldHJpZXZlZCBhdCBkYXkgMTQgLSBpLmUgNyBkYXlzIGFmdGVyIHRoZSBzd2l0Y2guIEZvciB0aGlzLCB3ZSB1c2UgZGlldC1zaWduYXR1cmUgZ2VuZXMgaWRlbnRpZmllZCBiZWZvcmUgdGhlIHN3aXRjaCB0byBoaWVyYXJjaGljYWxseSBjbHVzdGVyIHRoZSBncm91cHMuIERpZXQtc2lnbmF0dXJlIGdlbmVzIGFyZSBkZWZpbmVkIGhlcmUgYXMgdGhlIHRvcCAxMCBnZW5lcyBieSBudW1iZXIgb2YgZGF5cyAoUmVjb3JkLXNlcSkgb3IgcC1hZGogdmFsdWUgKFJOQS1zZXEpIGRldGVjdGVkIGFzIGVucmljaGVkIChsb2cyRkMgPiAyLjUpIHByaW9yIHRvIHRoZSBzd2l0Y2guIFdlIGNhbiBwZXJmZWN0bHkgY2xhc3NpZnkgZ3JvdXBzIHVzaW5nIFJlY29yZC1zZXEgZGF0YSwgd2hpbGUgZm9yIFJOQS1zZXEsIHRoZSBncm91cHMgY29udmVyZ2UuIAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjIsIGZpZy53aWR0aD0zfQpyaWJvc29tYWw8LWMoZ3JlcCgicnJzIiwgcmVjMS5nbG9iYWwuREVHJGdlbmVJRCksIGdyZXAoInJybCIsIHJlYzEuZ2xvYmFsLkRFRyRnZW5lSUQpKQppZihsZW5ndGgocmlib3NvbWFsKT4wKXsKICByZWMxLmdsb2JhbC5ERUc8LXJlYzEuZ2xvYmFsLkRFR1stcmlib3NvbWFsLF0KfQpnZW5lU2hvcnRMaXN0PC11bmlxdWUoYyhyZWMxLmdsb2JhbC5ERUdbd2hpY2gocmVjMS5nbG9iYWwuREVHJGxvZzJGb2xkQ2hhbmdlLm1heF9GQz4yLjUpLCAxXVsxOjEwXSwgcmVjMS5nbG9iYWwuREVHW3doaWNoKHJlYzEuZ2xvYmFsLkRFRyRsb2cyRm9sZENoYW5nZS5tYXhfU0M+Mi41KSwgMV1bMToxMF0scmVjMS5nbG9iYWwuREVHW3doaWNoKHJvd01lYW5zKHJlYzEuZ2xvYmFsLkRFR1ssMzo0XSk8KC0yLjUpKSwgMV1bMToxMF0pKQpjb2xzPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJkb2RnZXJibHVlNCIsICJ3aGl0ZSIsInZpb2xldHJlZDQiKSkoMjU2KQpkaGVhdG1hcDwtYXMuZGF0YS5mcmFtZSh0KGFwcGx5KHJlYzFfZGF5MTR0ZltnZW5lU2hvcnRMaXN0LF0sIDEsIHpzY29yZXN0YW5kYXJkaXplKSkpCmhlYXRtYXAucmVjMTwtcGhlYXRtYXAoZGhlYXRtYXAsIGFubm90YXRpb25fY29sID0gcmVjMWRfZGF5MTRbLDEsIGRyb3A9RkFMU0VdLCBhbm5vdGF0aW9uX2NvbG9ycz1jb2xvdXJfY29kZSwgdHJlZWhlaWdodF9yb3c9MCwgdHJlZWhlaWdodF9jb2w9NSwgZm9udHNpemUgPSA1LCBmb250c2l6ZV9yb3cgPSA1LCBmb250c2l6ZV9jb2wgPSA1LCBjbHVzdGVyX3Jvd3MgPSBGQUxTRSwgc2hvd19jb2xuYW1lcyA9IEZBTFNFLCBjb2xvciA9IGNvbHMsZm9udHNpemVfbnVtYmVyPTUsIG1haW49J0hpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9mIFJlY29yZC1zZXEgZGF0YSBvbiBkYXkgMTQgYmFzZWQgb24gREVHUyBkZXRlY3RlZCBvbiBkYXkgNycpCmhlYXRtYXAuZ2VuZWxpc3Q8LXJlYzFfZGF5MTR0ZltnZW5lU2hvcnRMaXN0LF0KY29sbmFtZXMoaGVhdG1hcC5nZW5lbGlzdCk8LXJlYzFkX2RheTE0JERpZXQKCgpjb2xzPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJkb2RnZXJibHVlNCIsICJ3aGl0ZSIsInZpb2xldHJlZDQiKSkoMjU2KQpnZW5lU2hvcnRMaXN0PC11bmlxdWUoYyhybmExLkRFR1t3aGljaChybmExLkRFRyRsb2cyRm9sZENoYW5nZS5GYXRfdnNfQ2hvdz4yLjUpLCAxXVsxOjEyXSwgcm5hMS5ERUdbd2hpY2gocm5hMS5ERUckbG9nMkZvbGRDaGFuZ2UuU3RhcmNoX3ZzX0Nob3c+Mi41KSwgMV1bMToxMl0scm5hMS5ERUdbd2hpY2gocm93TWVhbnMocm5hMS5ERUdbLDM6NF0pPCgtMi41KSksIDFdWzE6MTJdKSkKZGhlYXRtYXA8LWFzLmRhdGEuZnJhbWUodChhcHBseShybmExX2RheTE0dGZbZ2VuZVNob3J0TGlzdCxdLCAxLCB6c2NvcmVzdGFuZGFyZGl6ZSkpKQpkaGVhdG1hcDwtZGhlYXRtYXBbY29tcGxldGUuY2FzZXMoZGhlYXRtYXApLF0KaGVhdG1hcC5ybmExPC1waGVhdG1hcChkaGVhdG1hcCwgYW5ub3RhdGlvbl9jb2wgPSBybmExZF9kYXkxNFssMSwgZHJvcD1GQUxTRV0sIGFubm90YXRpb25fY29sb3JzPWNvbG91cl9jb2RlLCB0cmVlaGVpZ2h0X3Jvdz0wLCAgdHJlZWhlaWdodF9jb2w9NSwgZm9udHNpemUgPSA1LCBmb250c2l6ZV9yb3cgPSA1LCBmb250c2l6ZV9jb2wgPSA1LCBjbHVzdGVyX3Jvd3MgPSBGQUxTRSwgc2hvd19jb2xuYW1lcyA9IEZBTFNFLCBjb2xvciA9IGNvbHMsZm9udHNpemVfbnVtYmVyPTUsIG1haW49J0hpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9mIFJOQS1zZXEgZGF0YSBvbiBkYXkgMTQgYmFzZWQgb24gREVHUyBkZXRlY3RlZCBvbiBkYXkgNycpCgpgYGAKCiMgVHJhbnNpZW50IERpZXQgMgoKICBXZSBub3cgYW5hbHl6ZSBkYXRhIGZvciB0aGUgZXh0ZW5kZWQgdHJhbnNpZW50IGRpZXQgZXhwZXJpbWVudCB3aXRoIDIwIGRheXMuCgojIyBJbXBvcnRpbmcgYW5kIHByZS1wcm9jZXNzaW5nIGRhdGEgZm9yIHRyYW5zaWVudCBkaWV0IDIKICBXZSBpbXBvcnQgdGhlIGRhdGEgbWF0cmljZXMsIGZpbHRlciB0aGVtIGZvciBsb3dseSBleHByZXNzZWQgZ2VuZXMgYXMgd2VsbCBhcyBvdXRsaWVyIHNhbXBsZXMgd2l0aCBsb3cgY3VtdWxhdGl2ZSBjb3VudHMsIGFuZCB1c2UgdnN0IGZyb20gREVTZXEyIHRvIG5vcm1hbGl6ZSBhbmQgdHJhbnNmb3JtIHRoZSBkYXRhLiBXZSBhbHNvIGV4Y2x1ZGUgZGF5MSBmcm9tIHRoZSBhbmFseXNpcyBzaW5jZSB3ZSBoYXZlIGVtcGVyaWNhbGx5IG9ic2VydmVkIHRoYXQgdGhlIGRhdGEgYXJlIG5vaXN5IGZvciB0aGUgZmlyc3QgZGF5IG9mIGNvbG9uaXphdGlvbi4gV2UgdXNlIHRoZSBzYW1lIHRocmVzaG9sZHMgYXMgdGhlIHByZXZpb3VzIGV4cGVyaW1lbnQgZm9yIGNvbnNpc3RlbmN5LgogIApgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuMiwgZmlnLndpZHRoPTN9CnJlYzI8LWFzLmRhdGEuZnJhbWUocmVhZC50YWJsZSgiZGF0YS90cmFuc2llbnREaWV0Ml9SZWNvcmRzZXFfZ2Vub21lYWxpZ25pbmcudHh0IiwgaGVhZGVyID0gVFJVRSkpCnJlYzJkPC1hcy5kYXRhLmZyYW1lKHJlYWQudGFibGUoImRhdGEvdHJhbnNpZW50RGlldDJfUmVjb3Jkc2VxX21ldGFkYXRhLnR4dCIsIGhlYWRlciA9IFRSVUUpKQpERUxpc3Q8LXJlY29SZHNlcS5wcmVwcm9jZXNzKHJlYzIsIHJlYzJkLCBtaW5Db3VudHNQZXJTYW1wbGUgPSAxMDAwMCkKcmVjMjwtREVMaXN0W1sxXV0KcmVjMmQ8LURFTGlzdFtbMl1dCnJlYzJkPC1yZWMyZFtyZWMyZCREYXk+MSxdCnJlYzI8LXJlYzJbLHJvd25hbWVzKHJlYzJkKV0KcmVjMl90ZjwtcmVjb1Jkc2VxLnRyYW5zZm9ybShyZWMyLCByZWMyZCkKcm5hMjwtYXMuZGF0YS5mcmFtZShyZWFkLnRhYmxlKCJkYXRhL3RyYW5zaWVudERpZXQyX1JOQXNlcV9nZW5vbWVhbGlnbmluZy50eHQiLCBoZWFkZXIgPSBUUlVFKSkKcm5hMmQ8LWFzLmRhdGEuZnJhbWUocmVhZC50YWJsZSgiZGF0YS90cmFuc2llbnREaWV0Ml9STkFzZXFfbWV0YWRhdGEudHh0IiwgaGVhZGVyID0gVFJVRSkpCnJuYTJkPC1ybmEyZFssMTozXQpERUxpc3Q8LXJlY29SZHNlcS5wcmVwcm9jZXNzKHJuYTIsIHJuYTJkLCBtaW5Db3VudHNQZXJTYW1wbGUgPSAxMDAwMDApCnJuYTI8LURFTGlzdFtbMV1dCnJuYTJkPC1ERUxpc3RbWzJdXQpybmEyX3RmPC1yZWNvUmRzZXEudHJhbnNmb3JtKHJuYTIsIHJuYTJkKQpybmFkYXlzPC11bmlxdWUocm5hMmQkRGF5KQpgYGAKCiMjIERhdGEgZXhwbG9yYXRpb24KICBXZSB1c2UgUHJpbmNpcGFsIENvbXBvbmVudCBhbmFseXNpcyBhbmQgVU1BUCBmb3IgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIGFuZCBleHBsb3JpbmcgY2x1c3RlcnMgaW4gYW4gdW5zdXBlcnZpc2VkIGZhc2hpb24gaW4gb3VyIGRhdGEuICBXZSBmaXJzdCBnZW5lcmF0ZSB0aGVzZSBmb3IgdGhlIGVudGlyZSBkYXRhc2V0IGZyb20gUmVjb3JkLXNlcToKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi4yLCBmaWcud2lkdGg9M30KcmVjMnNkcyA8LSByb3dTZHMoYXMubWF0cml4KHJlYzJfdGYpKQpvIDwtIG9yZGVyKHJlYzJzZHMsIGRlY3JlYXNpbmcgPSBUUlVFKQpyZWMyUENBPC1wcmNvbXAodChyZWMyX3RmW29bMTo1MDBdLF0pKQpwY2Ffc3RhdDwtc3VtbWFyeShyZWMyUENBKQpyZWMyLnBjYV92YXJpYW5jZTwtcGNhX3N0YXQkaW1wb3J0YW5jZVsyLF0KcmVjMlBDQTwtYXMuZGF0YS5mcmFtZShyZWMyUENBJHgpCnJlYzJQQ0EkRGlldDwtcmVjMmQkRGlldApyZWMyUENBJERheTwtZmFjdG9yKHJlYzJkJERheSwgbGV2ZWxzID0gMToyMCkKcmVjMlBDQXBsb3Q8LWdncGxvdChyZWMyUENBLCBhZXMoeD1QQzEsIHk9UEMyLCBmaWxsPURpZXQsICBzaXplPURheSkpK2dlb21fcG9pbnQocGNoPTIxLCBjb2xvdXI9JyMwMDAwMDAnLCBzdHJva2U9MC4yNSkrdGhlbWVfcHViK3NjYWxlX3NpemVfZGlzY3JldGUocmFuZ2U9YygxLDIuNSkpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXQpKStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpKyBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MikpKSt5bGFiKHBhc3RlMCgiUEMyICgiLCBhcy5jaGFyYWN0ZXIocmVjMi5wY2FfdmFyaWFuY2VbMl0qMTAwKSwgIiUgdmFyaWFuY2UgZXhwbGFpbmVkKSIpKSt4bGFiKHBhc3RlMCgiUEMxICgiLCBhcy5jaGFyYWN0ZXIocmVjMi5wY2FfdmFyaWFuY2VbMV0qMTAwKSwgIiUgdmFyaWFuY2UgZXhwbGFpbmVkKSIpKStnZ3RpdGxlKCIgUENBIHBsb3Qgb2YgUmVjb3JkLXNlcSBkYXRhIikKCnJlYzJVTUFQPC11bWFwKHJlYzJQQ0FbLDE6KG5jb2wocmVjMlBDQSktMildLCBjdXN0b20uY29uZmlnKQpyZWMyVU1BUDwtYXMuZGF0YS5mcmFtZShyZWMyVU1BUCRsYXlvdXQpCnJlYzJVTUFQJERheTwtZmFjdG9yKHJlYzJkJERheSwgbGV2ZWxzID0gMToyMCkKcmVjMlVNQVAkRGlldDwtcmVjMmQkRGlldApjb2xuYW1lcyhyZWMyVU1BUClbMToyXTwtYygnVU1BUDEnLCdVTUFQMicpCnJlYzJVTUFQcGxvdDwtZ2dwbG90KHJlYzJVTUFQLCBhZXMoeD1VTUFQMSwgeT1VTUFQMiwgZmlsbD1EaWV0LCAgc2l6ZT1EYXkpKStnZW9tX3BvaW50KHBjaD0yMSwgY29sb3VyPScjMDAwMDAwJywgc3Ryb2tlPTAuMjUpK3RoZW1lX3B1YitzY2FsZV9zaXplX2Rpc2NyZXRlKHJhbmdlPWMoMSwyLjUpKStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0yKSkpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGFzLnZlY3Rvcihjb2xvdXJfY29kZSREaWV0KSkrZ2d0aXRsZSgiVU1BUCBwbG90IGZvciBSZWNvcmQtc2VxIGRhdGEgKGFsbCBkYXlzKSIpCgpyZWMyUENBcGxvdCtyZWMyVU1BUHBsb3QrcGxvdF9hbm5vdGF0aW9uKHRhZ19sZXZlbHMgPSAnQScpCgpgYGAKCgogIEZvciBhIGNvbXBhcmFzaW9uIGJldHdlZW4gUmVjb3JkLXNlcSBhbmQgUk5BLXNlcSwgd2UgZXhjbHVkZSB0aGUgZGF5cyBpbiBSZWNvcmQtc2VxIHRoYXQgZG9uJ3QgaGF2ZSBjb3JyZXNwb25kaW5nIFJOQS1zZXEgZGF0YS4gV2UgZmlyc3QgY2hlY2sgaWYgdGhlIGRpZXQgZ3JvdXBzIGNhbiBiZSBjbGFzc2lmaWVkIHVzaW5nIFBDQSBvbiBkYXkgNyAodGhlIGxhc3QgZGF5IHdoZW4gdGhlIG1pY2UgYXJlIGZlZCBkaWZmZXJlbnQgZGlldHMsIGJlZm9yZSBzd2l0Y2hpbmcgYWxsIG1pY2UgdG8gYSAnQ2hvdycgZGlldCkKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi4yLCBmaWcud2lkdGg9M30KCnJlYzJkX2RheTc8LXJlYzJkW3JlYzJkJERheT09NyxdCnJlYzJfZGF5NzwtcmVjMlssIHJvd25hbWVzKHJlYzJkX2RheTcpXQpyZWMyX2RheTd0ZjwtcmVjb1Jkc2VxLnRyYW5zZm9ybShyZWMyX2RheTcsIHJlYzJkX2RheTcpCnJlYzJfZGF5N19zZHMgPC0gcm93U2RzKGFzLm1hdHJpeChyZWMyX2RheTd0ZikpCm8gPC0gb3JkZXIocmVjMl9kYXk3X3NkcywgZGVjcmVhc2luZyA9IFRSVUUpCnJlYzJfZGF5N1BDQTwtcHJjb21wKHQocmVjMl9kYXk3dGZbb1sxOjUwMF0sXSkpCnBjYV9zdGF0PC1zdW1tYXJ5KHJlYzJfZGF5N1BDQSkKcGNhX3ZhcmlhbmNlPC1wY2Ffc3RhdCRpbXBvcnRhbmNlWzIsXQpyZWMyX2RheTdQQ0E8LWFzLmRhdGEuZnJhbWUocmVjMl9kYXk3UENBJHgpCnJlYzJfZGF5N1BDQSREaWV0PC1yZWMyZF9kYXk3JERpZXQKcmVjMl9kYXk3UENBJERheTwtZmFjdG9yKHJlYzJkX2RheTckRGF5LCBsZXZlbHMgPSBjKDcpKQpyZWMyX2RheTdQQ0FwbG90PC1nZ3Bsb3QocmVjMl9kYXk3UENBLCBhZXMoeD1QQzEsIHk9UEMyLCBmaWxsPURpZXQsICBzaXplPURheSkpK2dlb21fcG9pbnQocGNoPTIxLCBjb2xvdXI9JyMwMDAwMDAnLCBzdHJva2U9MC4yNSkrdGhlbWVfcHViK3NjYWxlX3NpemVfZGlzY3JldGUocmFuZ2U9YygyLDIuNSkpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXQpKStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpKyBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MikpKSt5bGFiKHBhc3RlMCgiUEMyICgiLCBhcy5jaGFyYWN0ZXIocGNhX3ZhcmlhbmNlWzJdKjEwMCksICIlIHZhcmlhbmNlIGV4cGxhaW5lZCkiKSkreGxhYihwYXN0ZTAoIlBDMSAoIiwgYXMuY2hhcmFjdGVyKHBjYV92YXJpYW5jZVsxXSoxMDApLCAiJSB2YXJpYW5jZSBleHBsYWluZWQpIikpK2dndGl0bGUoIiBQQ0EgcGxvdCBvZiBSZWNvcmQtc2VxIGRhdGEgb24gZGF5IDcgIikKCnJuYTJkX2RheTc8LXJuYTJkW3JuYTJkJERheT09NyxdCnJuYTJfZGF5Nzwtcm5hMlssIHJvd25hbWVzKHJuYTJkX2RheTcpXQpybmEyX2RheTd0ZjwtcmVjb1Jkc2VxLnRyYW5zZm9ybShybmEyX2RheTcsIHJuYTJkX2RheTcpCnJuYTJfZGF5N19zZHMgPC0gcm93U2RzKGFzLm1hdHJpeChybmEyX2RheTd0ZikpCm8gPC0gb3JkZXIocm5hMl9kYXk3X3NkcywgZGVjcmVhc2luZyA9IFRSVUUpCnJuYTJfZGF5N1BDQTwtcHJjb21wKHQocm5hMl9kYXk3dGZbb1sxOjUwMF0sXSkpCnBjYV9zdGF0PC1zdW1tYXJ5KHJuYTJfZGF5N1BDQSkKcGNhX3ZhcmlhbmNlPC1wY2Ffc3RhdCRpbXBvcnRhbmNlWzIsXQpybmEyX2RheTdQQ0E8LWFzLmRhdGEuZnJhbWUocm5hMl9kYXk3UENBJHgpCnJuYTJfZGF5N1BDQSREaWV0PC1ybmEyZF9kYXk3JERpZXQKcm5hMl9kYXk3UENBJERheTwtZmFjdG9yKHJuYTJkX2RheTckRGF5LCBsZXZlbHMgPSBjKDcpKQpybmEyX2RheTdQQ0FwbG90PC1nZ3Bsb3Qocm5hMl9kYXk3UENBLCBhZXMoeD1QQzEsIHk9UEMyLCBmaWxsPURpZXQsICBzaXplPURheSkpK2dlb21fcG9pbnQocGNoPTIxLCBjb2xvdXI9JyMwMDAwMDAnLCBzdHJva2U9MC4yNSkrdGhlbWVfcHViK3NjYWxlX3NpemVfZGlzY3JldGUocmFuZ2U9YygyLDIuNSkpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXQpKStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpKyBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MikpKSt5bGFiKHBhc3RlMCgiUEMyICgiLCBhcy5jaGFyYWN0ZXIocGNhX3ZhcmlhbmNlWzJdKjEwMCksICIlIHZhcmlhbmNlIGV4cGxhaW5lZCkiKSkreGxhYihwYXN0ZTAoIlBDMSAoIiwgYXMuY2hhcmFjdGVyKHBjYV92YXJpYW5jZVsxXSoxMDApLCAiJSB2YXJpYW5jZSBleHBsYWluZWQpIikpK2dndGl0bGUoIiBQQ0EgcGxvdCBvZiBSTkEtc2VxIGRhdGEgb24gZGF5IDcgIikKCgpyZWMyX2RheTdQQ0FwbG90K3JuYTJfZGF5N1BDQXBsb3QrcGxvdF9hbm5vdGF0aW9uKHRhZ19sZXZlbHMgPSAnQScpCgpgYGAKCiAgV2UgdGhlbiBjb21wYXJlIHRoZSB0ZW1wb3JhbCB0cmFqZWN0b3JpZXMgb2YgUmVjb3JkLXNlcSBhbmQgUk5BLXNlcSB1c2luZyBVTUFQcy4gUmVjb3JkLXNlcSBkYXRhIHJldGFpbnMgaW5mb3JtYXRpb24gYWJvdXQgcHJpb3IgZGlldCBncm91cHMgdGlsbCB0aGUgZmluYWwgZGF5LCBhbmQgY2x1c3RlcnMgc3Ryb25nbHkgYmFzZWQgb24gZ3JvdXA7IHdoZXJlYXMgUk5BLXNlcSBkYXRhIGhhcyBhIHByb25vdW5jZWQgdGVtcG9yYWwgY2hhbmdlLCBhbmQgaW5pdGlhbCBjbHVzdGVycyBmb3IgZGlmZmVyZW50IGRpZXQgZ3JvdXBzIHF1aWNrbHkgY29udmVyZ2UuCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuMiwgZmlnLndpZHRoPTN9CnJlYzJkX3JuYTwtcmVjMmRbd2hpY2gocmVjMmQkRGF5JWluJXJuYWRheXMpLF0KcmVjMl9ybmE8LXJlYzJbLCByb3duYW1lcyhyZWMyZF9ybmEpXQpyZWMyX3JuYXRmPC1yZWNvUmRzZXEudHJhbnNmb3JtKHJlYzJfcm5hLCByZWMyZF9ybmEpCnJlYzJybmFzZHMgPC0gcm93U2RzKGFzLm1hdHJpeChyZWMyX3JuYXRmKSkKbyA8LSBvcmRlcihyZWMycm5hc2RzLCBkZWNyZWFzaW5nID0gVFJVRSkKcmVjMnJuYVBDQTwtcHJjb21wKHQocmVjMl9ybmF0ZltvWzE6NTAwXSxdKSkKcGNhX3N0YXQ8LXN1bW1hcnkocmVjMnJuYVBDQSkKcmVjMnJuYS5wY2FfdmFyaWFuY2U8LXBjYV9zdGF0JGltcG9ydGFuY2VbMixdCnJlYzJybmFQQ0E8LWFzLmRhdGEuZnJhbWUocmVjMnJuYVBDQSR4KQpyZWMycm5hUENBJERpZXQ8LXJlYzJkX3JuYSREaWV0CnJlYzJybmFQQ0EkRGF5PC1mYWN0b3IocmVjMmRfcm5hJERheSwgbGV2ZWxzID0gcm5hZGF5cykKcmVjMnJuYVVNQVA8LXVtYXAocmVjMnJuYVBDQVssMToobmNvbChyZWMycm5hUENBKS0yKV0sIGN1c3RvbS5jb25maWcpCnJlYzJybmFVTUFQPC1hcy5kYXRhLmZyYW1lKHJlYzJybmFVTUFQJGxheW91dCkKcmVjMnJuYVVNQVAkRGF5PC1mYWN0b3IocmVjMmRfcm5hJERheSwgbGV2ZWxzID0gcm5hZGF5cykKcmVjMnJuYVVNQVAkRGlldDwtcmVjMmRfcm5hJERpZXQKY29sbmFtZXMocmVjMnJuYVVNQVApWzE6Ml08LWMoJ1VNQVAxJywnVU1BUDInKQpyZWMycm5hVU1BUHBsb3Q8LWdncGxvdChyZWMycm5hVU1BUCwgYWVzKHg9VU1BUDEsIHk9VU1BUDIsIGZpbGw9RGlldCwgIHNpemU9RGF5KSkrZ2VvbV9wb2ludChwY2g9MjEsIGNvbG91cj0nIzAwMDAwMCcsIHN0cm9rZT0wLjI1KSt0aGVtZV9wdWIrc2NhbGVfc2l6ZV9kaXNjcmV0ZShyYW5nZT1jKDEsMi41KSkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpKyBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MikpKStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBhcy52ZWN0b3IoY29sb3VyX2NvZGUkRGlldCkpK2dndGl0bGUoIlVNQVAgcGxvdCBmb3IgUmVjb3JkLXNlcSBkYXRhIikKCnJuYTJzZHMgPC0gcm93U2RzKGFzLm1hdHJpeChybmEyX3RmKSkKbyA8LSBvcmRlcihybmEyc2RzLCBkZWNyZWFzaW5nID0gVFJVRSkKcm5hMlBDQTwtcHJjb21wKHQocm5hMl90ZltvWzE6NTAwXSxdKSkKcGNhX3N0YXQ8LXN1bW1hcnkocm5hMlBDQSkKcm5hMi5wY2FfdmFyaWFuY2U8LXBjYV9zdGF0JGltcG9ydGFuY2VbMixdCnJuYTJQQ0E8LWFzLmRhdGEuZnJhbWUocm5hMlBDQSR4KQpybmEyUENBJERpZXQ8LXJuYTJkJERpZXQKcm5hMlBDQSREYXk8LWZhY3RvcihybmEyZCREYXksIGxldmVscyA9IHJuYWRheXMpCnJuYTJVTUFQPC11bWFwKHJuYTJQQ0FbLDE6KG5jb2wocm5hMlBDQSktMildLCBjdXN0b20uY29uZmlnKQpybmEyVU1BUDwtYXMuZGF0YS5mcmFtZShybmEyVU1BUCRsYXlvdXQpCnJuYTJVTUFQJERheTwtZmFjdG9yKHJuYTJkJERheSwgbGV2ZWxzID0gcm5hZGF5cykKcm5hMlVNQVAkRGlldDwtcm5hMmQkRGlldApjb2xuYW1lcyhybmEyVU1BUClbMToyXTwtYygnVU1BUDEnLCdVTUFQMicpCnJuYTJVTUFQcGxvdDwtZ2dwbG90KHJuYTJVTUFQLCBhZXMoeD1VTUFQMSwgeT1VTUFQMiwgZmlsbD1EaWV0LCAgc2l6ZT1EYXkpKStnZW9tX3BvaW50KHBjaD0yMSwgY29sb3VyPScjMDAwMDAwJywgc3Ryb2tlPTAuMjUpK3RoZW1lX3B1YitzY2FsZV9zaXplX2Rpc2NyZXRlKHJhbmdlPWMoMSwyLjUpKStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0yKSkpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGFzLnZlY3Rvcihjb2xvdXJfY29kZSREaWV0KSkrZ2d0aXRsZSgiVU1BUCBwbG90IGZvciBSTkEtc2VxIGRhdGEiKQpyZWMycm5hVU1BUHBsb3Qrcm5hMlVNQVBwbG90K3Bsb3RfYW5ub3RhdGlvbih0YWdfbGV2ZWxzID0gJ0EnKQoKYGBgCgoKIyMgRGlzY292ZXJ5IG9mIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcyAoREVHcyk6CiAgV2UgaWRlbnRpZnkgREVHcyBmb3IgZGF5IDcgKHNpbmNlIHRoaXMgaXMgdGhlIGZpbmFsIGRheSB3aGVuIHRoZSBtaWNlIGFyZSBmZWQgc2VwYXJhdGUgZGlldHMsIGFuZCBSTkEtc2VxIGlzIGFsc28gYXZhaWxhYmxlIGZvciB0aGlzIGRheSkuIFdlIGRlZmluZSBERUdzIGFzIGdlbmVzIGlkZW50aWZpZWQgdG8gYmUgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgdXNpbmcgYSB0aHJlc2hvbGQgKHBhZGogPCAwLjA1KSBieSBib3RoIERFU2VxMiBhbmQgZWRnZVIgKG11bHRpcGxlIHRlc3RpbmcsIHNpbmNlIHdlIGhhdmUgMyBncm91cHMpLiAgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpyZWMyLmRlc2VxPC1yZWNvUmRzZXEuREUocmVjMl9kYXk3LCByZWMyZF9kYXk3LCB0b29sPSdERVNlcTInKQpyZWMyLmVkZ2VyPC1yZWNvUmRzZXEuREUocmVjMl9kYXk3LCByZWMyZF9kYXk3LCB0b29sPSdlZGdlUicpCnJlYzIuZGVzZXEuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMi5kZXNlcSwgcCA9IDAuMDUpCnJlYzIuZWRnZXIuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMi5lZGdlciwgcCA9IDAuMDUpCnJlYzIuREVHPC1yZWMyLmRlc2VxW2ludGVyc2VjdChyZWMyLmRlc2VxLmdlbmVzLCByZWMyLmVkZ2VyLmdlbmVzKSwgYygxLDcsIHdoaWNoKGdyZXBsKCdsb2cyRm9sZENoYW5nZScsIGNvbG5hbWVzKHJlYzIuZGVzZXEpKSkpXQpyZWMyLkRFRyRnZW5lSUQ8LWFzLmNoYXJhY3RlcihyZWMyLkRFRyRnZW5lSUQpCnJlYzIuREVHPC1yZWMyLkRFR1tvcmRlcihyZWMyLkRFRyRwYWRqKSxdCnJpYm9zb21hbDwtYyhncmVwKCJycnMiLCByb3duYW1lcyhyZWMyLkRFRykpLCBncmVwKCJycmwiLCByb3duYW1lcyhyZWMyLkRFRykpKQppZihsZW5ndGgocmlib3NvbWFsKT4wKXsKICByZWMyLkRFRzwtcmVjMi5ERUdbLXJpYm9zb21hbCxdCn0Kcm5hMi5kZXNlcTwtcmVjb1Jkc2VxLkRFKHJuYTJfZGF5Nywgcm5hMmRfZGF5NywgdG9vbD0nREVTZXEyJykKcm5hMi5lZGdlcjwtcmVjb1Jkc2VxLkRFKHJuYTJfZGF5Nywgcm5hMmRfZGF5NywgdG9vbD0nZWRnZVInKQpybmEyLmRlc2VxLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJuYTIuZGVzZXEsIHAgPSAwLjA1KQpybmEyLmVkZ2VyLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJuYTIuZWRnZXIsIHAgPSAwLjA1KQpybmEyLkRFRzwtcm5hMi5kZXNlcVtpbnRlcnNlY3Qocm5hMi5kZXNlcS5nZW5lcywgcm5hMi5lZGdlci5nZW5lcyksIGMoMSw3LCB3aGljaChncmVwbCgnbG9nMkZvbGRDaGFuZ2UnLCBjb2xuYW1lcyhybmEyLmRlc2VxKSkpKV0Kcm5hMi5ERUckZ2VuZUlEPC1hcy5jaGFyYWN0ZXIocm5hMi5ERUckZ2VuZUlEKQpybmEyLkRFRzwtcm5hMi5ERUdbb3JkZXIocm5hMi5ERUckcGFkaiksXQpyaWJvc29tYWw8LWMoZ3JlcCgicnJzIiwgcm93bmFtZXMocm5hMi5ERUcpKSwgZ3JlcCgicnJsIiwgcm93bmFtZXMocm5hMi5ERUcpKSkKaWYobGVuZ3RoKHJpYm9zb21hbCk+MCl7CiAgcm5hMi5ERUc8LXJuYTIuREVHWy1yaWJvc29tYWwsXQp9CnJlYzIubm92ZWw8LXJlYzIuREVHWy13aGljaChyZWMyLkRFRyRnZW5lSUQlaW4lcm5hMi5ERUckZ2VuZUlEKSxdCmBgYAogIAogIFdlIGFsc28gbG9vayBmb3IgREUgZ2VuZXMgb3ZlciBkYXlzIDItNyBpbiBSZWNvcmQtc2VxIHVzaW5nIGEgbG9vc2VyIGNvbmZpZGVuY2UgdGhyZXNob2xkIChwYWRqIDwwLjEpIHRvIGlkZW50aWZ5IGNvbnNpc3RlbnQgZGlldC1zaWduYXR1cmUgZ2VuZXMuCiAgICAKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcmVjMi5ERUcubGlzdDwtbGlzdCgpCnJlYzIuZXI8LWxpc3QoKQpyZWMyLmRlPC1saXN0KCkKcmVjMi5nbG9iYWwuREVHPC1jKCkKZm9yKGkgaW4gdW5pcXVlKHJlYzJkJERheSkpewogIGlmKGk8OCl7CiAgICBkdDwtcmVjMmRbd2hpY2gocmVjMmQkRGF5PT1pKSwgMSwgZHJvcD1GQUxTRV0KICAgIGR0JERpZXQ8LWZhY3RvcihkdCREaWV0KQogICAgZGU8LXJlYzJbLHdoaWNoKGNvbG5hbWVzKHJlYzIpJWluJXJvd25hbWVzKGR0KSldCiAgICByZWMyLmRlW1tpXV08LXJlY29SZHNlcS5ERShkZSxkdCx0b29sPSdERVNlcTInKQogICAgcmVjMi5lcltbaV1dPC1yZWNvUmRzZXEuREUoZGUsZHQsdG9vbD0nZWRnZVInKQogICAgcmVjMi5kZS5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhyZWMyLmRlW1tpXV0sIHAgPSAwLjEpCiAgICByZWMyLmVyLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzIuZXJbW2ldXSwgcCA9IDAuMSkKICAgIHJlYzIuREVHLmxpc3RbW2ldXTwtcmVjMi5kZVtbaV1dW2ludGVyc2VjdChyZWMyLmRlLmdlbmVzLCByZWMyLmVyLmdlbmVzKSwgYygxLDcsIHdoaWNoKGdyZXBsKCdsb2cyRm9sZENoYW5nZScsIGNvbG5hbWVzKHJlYzIuZGVbW2ldXSkpKSldCiAgICByZWMyLmdsb2JhbC5ERUc8LSBjKHJlYzIuZ2xvYmFsLkRFRywgYXMuY2hhcmFjdGVyKGludGVyc2VjdChyZWMyLmRlLmdlbmVzLHJlYzIuZXIuZ2VuZXMpKSkKICB9Cn0KcmVjMi5nbG9iYWwuREVHMTwtYXMuZGF0YS5mcmFtZSh0YWJsZShyZWMyLmdsb2JhbC5ERUcpW29yZGVyKHRhYmxlKHJlYzIuZ2xvYmFsLkRFRyksIGRlY3JlYXNpbmcgPSBUUlVFKV0pCnJlYzIuZ2xvYmFsLkRFRzI8LWFzLmRhdGEuZnJhbWUodGFibGUocmVjMi5nbG9iYWwuREVHKVtvcmRlcih0YWJsZShyZWMyLmdsb2JhbC5ERUcpLCBkZWNyZWFzaW5nID0gVFJVRSldKQpjb2xuYW1lcyhyZWMyLmdsb2JhbC5ERUcxKTwtYygiZ2VuZUlEIiwgImRheXNfREUiKQpjb2xuYW1lcyhyZWMyLmdsb2JhbC5ERUcyKTwtYygiZ2VuZUlEIiwgImRheXNfREUiKQpyZWMyLmdsb2JhbC5ERUcxJGdlbmVJRDwtYXMuY2hhcmFjdGVyKHJlYzIuZ2xvYmFsLkRFRzEkZ2VuZUlEKQpyZWMyLmdsb2JhbC5ERUcyJGdlbmVJRDwtYXMuY2hhcmFjdGVyKHJlYzIuZ2xvYmFsLkRFRzIkZ2VuZUlEKQpmb3IoaSBpbiB1bmlxdWUocmVjMmQkRGF5KSl7CiAgaWYoaTw4KXsKICByZWMyLmdsb2JhbC5ERUcxJFYxPC1yZWMyLmRlW1tpXV1bcmVjMi5nbG9iYWwuREVHMSRnZW5lSUQsNF0KICBjb2xuYW1lcyhyZWMyLmdsb2JhbC5ERUcxKVtuY29sKHJlYzIuZ2xvYmFsLkRFRzEpXTwtcGFzdGUwKCJsb2cyRm9sZENoYW5nZV9GQ19kYXkiLCBpKQogIHJlYzIuZ2xvYmFsLkRFRzIkVjE8LXJlYzIuZGVbW2ldXVtyZWMyLmdsb2JhbC5ERUcyJGdlbmVJRCw4XQogIGNvbG5hbWVzKHJlYzIuZ2xvYmFsLkRFRzIpW25jb2wocmVjMi5nbG9iYWwuREVHMildPC1wYXN0ZTAoImxvZzJGb2xkQ2hhbmdlX1NDX2RheSIsIGkpCgogIH0KfQpyZWMyLmdsb2JhbC5ERUcxJGxvZzJGb2xkQ2hhbmdlLm1heDwtcmVjMi5nbG9iYWwuREVHMVssMzpuY29sKHJlYzIuZ2xvYmFsLkRFRzEpXVtjYmluZCgxOm5yb3cocmVjMi5nbG9iYWwuREVHMVssMzpuY29sKHJlYzIuZ2xvYmFsLkRFRzEpXSksIG1heC5jb2wocmVwbGFjZSh4IDwtIGFicyhyZWMyLmdsb2JhbC5ERUcxWywzOm5jb2wocmVjMi5nbG9iYWwuREVHMSldKSwgaXMubmEoeCksIC1JbmYpKSldCnJlYzIuZ2xvYmFsLkRFRzIkbG9nMkZvbGRDaGFuZ2UubWF4PC1yZWMyLmdsb2JhbC5ERUcyWywzOm5jb2wocmVjMi5nbG9iYWwuREVHMildW2NiaW5kKDE6bnJvdyhyZWMyLmdsb2JhbC5ERUcyWywzOm5jb2wocmVjMi5nbG9iYWwuREVHMildKSwgbWF4LmNvbChyZXBsYWNlKHggPC0gYWJzKHJlYzIuZ2xvYmFsLkRFRzJbLDM6bmNvbChyZWMyLmdsb2JhbC5ERUcyKV0pLCBpcy5uYSh4KSwgLUluZikpKV0KCnJlYzIuZ2xvYmFsLkRFRzwtcmVjMi5nbG9iYWwuREVHMVssYygxLDIsbmNvbChyZWMyLmdsb2JhbC5ERUcxKSldCnJlYzIuZ2xvYmFsLkRFRyRWMTwtcmVjMi5nbG9iYWwuREVHMlssbmNvbChyZWMyLmdsb2JhbC5ERUcyKV0KY29sbmFtZXMocmVjMi5nbG9iYWwuREVHKVszXTwtImxvZzJGb2xkQ2hhbmdlLm1heF9GQyIKY29sbmFtZXMocmVjMi5nbG9iYWwuREVHKVs0XTwtImxvZzJGb2xkQ2hhbmdlLm1heF9TQyIKCmBgYAogCiAKIyMgUGxvdHRpbmcgaW5kaXZpZHVhbCBERUdzOgogIFdlIHBsb3QgdnN0LXRyYW5zZm9ybWVkIGdlbm9tZS1hbGlnbmluZyBzcGFjZXIgY291bnRzIGZvciA2IGdlbmVzIGluIHRoZSBnbnRSIHBhdGh3YXkgZm9yIGNob3cgYW5kIHN0YXJjaCBmZWQgbWljZSBvbiBkYXkgNy4KICAKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjIsIGZpZy53aWR0aD0zfQpnbnRSX2dlbmVzPC1jKCdlZGEnLCdlZGQnLCAnZ250VCcsICdrZGdUJywgJ2dudFUnLCAnZ250SycpCmRlPC1yZWMyZF9kYXk3W3JlYzJkX2RheTckRGlldCE9J0ZhdCcsXQpkdDwtcmVjMl9kYXk3dGZbZ250Ul9nZW5lcywgcm93bmFtZXMoZGUpXQpyZWMyLmdudFIucGxvdC5kZjwtZGF0YS5mcmFtZShEaWV0PWRlJERpZXQsIHQoZHQpKQpyZWMyLmdudFIucGxvdC5kZjwtbWVsdChyZWMyLmdudFIucGxvdC5kZiwgaWQudmFycyA9ICdEaWV0JykKcmVjMi5nbnRSLnBsb3Q8LWdncGxvdChyZWMyLmdudFIucGxvdC5kZiwgYWVzKHk9dmFsdWUsIHg9dmFyaWFibGUsIGZpbGw9RGlldCwgY29sb3I9J2JsYWNrJykpK2dlb21fYm94cGxvdChzaXplPTAuMjQsIG91dGxpZXIuc2l6ZT0wKStnZW9tX3BvaW50KHNpemU9MC40OCwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSgwLjc1KSkrdGhlbWVfcHViK3lsYWIoImdlbmUtYWxpZ25pbmcgc3BhY2VyIGNvdW50cyAodnN0LXRyYW5zZm9ybWVkKSIpK3hsYWIoImdlbmUiKStnZ3RpdGxlKCJSZWNvcmQtc2VxIGNvdW50cyBvbiBkYXkgNyBmb3IgZ250UiBnZW5lIikrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXRbYygxLDMpXSkpK3NjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJibGFjayIpLCBndWlkZT0nbm9uZScpCnJlYzIuZ250Ui5wbG90K3Bsb3RfYW5ub3RhdGlvbigpCgpgYGAKCiMjIEhlYXRtYXAgZm9yIFJlY29yZC1zZXEgYW5kIFJOQS1zZXEgREVHcyBvbiBkYXkgNwogIFdlIHBsb3QgaGVhdG1hcHMgc2hvd2luZyBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBvZiBzYW1wbGVzIHVzaW5nIGRldGVjdGVkIERFIGdlbmVzIGZvciBib3RoIFJlY29yZC1zZXEgYW5kIFJOQS1zZXEgb24gZGF5IDcuIAogIApgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuMiwgZmlnLndpZHRoPTN9CmNvbHM8LSBjb2xvclJhbXBQYWxldHRlKGMoImRvZGdlcmJsdWU0IiwgIndoaXRlIiwidmlvbGV0cmVkNCIpKSgyNTYpCmRoZWF0bWFwPC1hcy5kYXRhLmZyYW1lKHQoYXBwbHkocmVjMl9kYXk3dGZbcmVjMi5ERUckZ2VuZUlELF0sIDEsIHpzY29yZXN0YW5kYXJkaXplKSkpCmhlYXRtYXAucmVjMi5kYXk3PC1waGVhdG1hcChkaGVhdG1hcCwgYW5ub3RhdGlvbl9jb2wgPSByZWMyZF9kYXk3WywxLCBkcm9wPUZBTFNFXSwgYW5ub3RhdGlvbl9jb2xvcnM9Y29sb3VyX2NvZGUsIGZvbnRzaXplID0gNSwgZm9udHNpemVfcm93ID0gNSwgZm9udHNpemVfY29sID0gNSwgY2x1c3Rlcl9yb3dzID0gVFJVRSwgdHJlZWhlaWdodF9yb3cgPSAwLCAgdHJlZWhlaWdodF9jb2wgPSA1LCBzaG93X2NvbG5hbWVzID0gRkFMU0UsIHNob3dfcm93bmFtZXMgPSBGQUxTRSwgY29sb3IgPSBjb2xzLGZvbnRzaXplX251bWJlcj01LCB3aWR0aD0yLjI4LCBoZWlnaHQ9Mi4yOCwgbWFpbj0nUmVjb3JkLXNlcSBERUdzIGRheSA3JykKaGVhdG1hcC5nZW5lbGlzdDwtaGVhdG1hcC5yZWMyLmRheTckdHJlZV9yb3ckbGFiZWxzW2hlYXRtYXAucmVjMi5kYXk3JHRyZWVfcm93JG9yZGVyXQpoZWF0bWFwLmdlbmVsaXN0PC1yZWMyX2RheTd0ZltoZWF0bWFwLmdlbmVsaXN0LF0KY29sbmFtZXMoaGVhdG1hcC5nZW5lbGlzdCk8LWFzLmNoYXJhY3RlcihyZWMyZF9kYXk3JERpZXQpCgpjb2xzPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJkb2RnZXJibHVlNCIsICJ3aGl0ZSIsInZpb2xldHJlZDQiKSkoMjU2KQpkaGVhdG1hcDwtYXMuZGF0YS5mcmFtZSh0KGFwcGx5KHJuYTJfZGF5N3RmW3JuYTIuREVHJGdlbmVJRCxdLCAxLCB6c2NvcmVzdGFuZGFyZGl6ZSkpKQpoZWF0bWFwLnJuYTIuZGF5NzwtcGhlYXRtYXAoZGhlYXRtYXAsIGFubm90YXRpb25fY29sID0gcm5hMmRfZGF5N1ssMSwgZHJvcD1GQUxTRV0sIGFubm90YXRpb25fY29sb3JzPWNvbG91cl9jb2RlLCBmb250c2l6ZSA9IDUsIGZvbnRzaXplX3JvdyA9IDUsIGZvbnRzaXplX2NvbCA9IDUsIGNsdXN0ZXJfcm93cyA9IFRSVUUsIHRyZWVoZWlnaHRfcm93ID0gMCwgdHJlZWhlaWdodF9jb2wgPSA1LCBzaG93X2NvbG5hbWVzID0gRkFMU0Usc2hvd19yb3duYW1lcyA9IEZBTFNFLCBjb2xvciA9IGNvbHMsZm9udHNpemVfbnVtYmVyPTUsIHdpZHRoPTIuMjgsIGhlaWdodD0yLjI4LCBtYWluPSdSTkEtc2VxIERFR3MgZGF5IDcnKQpgYGAKICAKIyMgVm9sY2FubyBwbG90cyBhbmQgaGVhdG1hcHMgZm9yIFJlY29yZC1zZXEgYW5kIFJOQS1zZXEgREVHczoKV2UgcGVyZm9ybSBwYWlyd2lzZSBERSBhbmFseXNpcyB1c2luZyBERVNlcTIgdG8gaWRlbnRpZnkgbG9nMkZDIGFuZCBwLWFkaiB2YWx1ZXMgZm9yIGVhY2ggZGlldCBwYWlyIG9uIGRheSA3LCBhbmQgcGxvdCB2b2xjYW5vZXMgKGxvZzJGQz4xLjUsIHBhZGo8MC4xKQogICAKICBSZWNvcmQtc2VxOiAKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjIsIGZpZy53aWR0aD0zfQpsZXZlbHM8LXNvcnQodW5pcXVlKHJlYzJkX2RheTdbLDFdKSkKcGFpcndpc2UuY29tYm88LWNvbWJuKGxldmVscywgMikKY29sb3IuY29tYm88LWNvbWJuKGNvbG91cl9jb2RlJERpZXQsIDIpCnJlYzIuZGUudmFsczwtbGlzdCgpCnJlYzIuZWQudmFsczwtbGlzdCgpCnZvbC5wbG90czwtbGlzdCgpCkRFRzwtbGlzdCgpCmZvcihpIGluIDE6ZGltKHBhaXJ3aXNlLmNvbWJvKVsyXSl7CiAgZHM8LXJlYzJkX2RheTdbd2hpY2gocmVjMmRfZGF5N1ssMV0laW4lcGFpcndpc2UuY29tYm9bLGldKSxdCiAgZHMkRGlldDwtYXMuY2hhcmFjdGVyKGRzJERpZXQpCiAgZHQ8LXJlYzJfZGF5N1sscm93bmFtZXMoZHMpXQogIGR0ZjwtcmVjb1Jkc2VxLnRyYW5zZm9ybShkdCxkcykKICByZWMyLmRlLnZhbHNbW2ldXSA8LSByZWNvUmRzZXEuREUoZHQsIGRzLCB0b29sID0gJ0RFU2VxMicpCiAgcmVjMi5lZC52YWxzW1tpXV0gPC0gcmVjb1Jkc2VxLkRFKGR0LCBkcywgdG9vbCA9ICdlZGdlUicpCiAgcm93bmFtZXMocmVjMi5kZS52YWxzW1tpXV0pIDwtIHJlYzIuZGUudmFsc1tbaV1dJGdlbmVJRAogIHJvd25hbWVzKHJlYzIuZWQudmFsc1tbaV1dKSA8LSByZWMyLmVkLnZhbHNbW2ldXSRnZW5lSUQKICBkZXNlcS5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhyZWMyLmRlLnZhbHNbW2ldXSwgcCA9IDAuMSkKICBlZGdlci5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhyZWMyLmVkLnZhbHNbW2ldXSwgcCA9IDAuMSkKICBERUdbW2ldXTwtZGF0YS5mcmFtZShyb3cubmFtZXM9aW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksZ2VuZUlEPWludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLGxvZzJGb2xkY2hhbmdlPXJlYzIuZGUudmFsc1tbaV1dW2ludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLCA0XSwgcGFkaj1yZWMyLmRlLnZhbHNbW2ldXVtpbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSwgN10pCiAgcmlib3NvbWFsPC1jKGdyZXAoInJycyIsIHJvd25hbWVzKERFR1tbaV1dKSksIGdyZXAoInJybCIsIHJvd25hbWVzKERFR1tbaV1dKSkpCiAgaWYobGVuZ3RoKHJpYm9zb21hbCk+MCl7CiAgICAgIERFR1tbaV1dPC1ERUdbW2ldXVstcmlib3NvbWFsLF0KICB9CiAgREVHW1tpXV0kZ2VuZUlEPC1hcy5jaGFyYWN0ZXIoREVHW1tpXV0kZ2VuZUlEKQogIHJlYzIuZGUudmFsc1tbaV1dPC1yZWMyLmRlLnZhbHNbW2ldXVtjb21wbGV0ZS5jYXNlcyhyZWMyLmRlLnZhbHNbW2ldXSksXQogIHJlYzIuZGUudmFsc1tbaV1dJEdyb3VwPC0nTm9uZScKICByZWMyLmRlLnZhbHNbW2ldXSRHcm91cFsgd2hpY2gocmVjMi5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2U+MS41JnJlYzIuZGUudmFsc1tbaV1dJHBhZGo8MC4xKV08LXBhc3RlMCgidXByZWd1bGF0ZWQuaW4uIiwgYXMuY2hhcmFjdGVyKHNvcnQodW5pcXVlKGRzJERpZXQpKVsyXSkpCiAgcmVjMi5kZS52YWxzW1tpXV0kR3JvdXBbIHdoaWNoKHJlYzIuZGUudmFsc1tbaV1dJGxvZzJGb2xkQ2hhbmdlPCgtMS41KSZyZWMyLmRlLnZhbHNbW2ldXSRwYWRqPDAuMSldPC1wYXN0ZTAoInVwcmVndWxhdGVkLmluLiIsIHNvcnQodW5pcXVlKGRzJERpZXQpKVsxXSkKIHJlYzIuZGUudmFsc1tbaV1dJEdyb3VwPC1mYWN0b3IocmVjMi5kZS52YWxzW1tpXV0kR3JvdXAsIGxldmVscyA9IGMocGFzdGUwKCJ1cHJlZ3VsYXRlZC5pbi4iLCBhcy5jaGFyYWN0ZXIoc29ydCh1bmlxdWUoZHMkRGlldCkpWzFdKSksIHBhc3RlMCgidXByZWd1bGF0ZWQuaW4uIiwgc29ydCh1bmlxdWUoZHMkRGlldCkpWzJdKSwgJ05vbmUnICkpCiAgcmVjMi5kZS52YWxzW1tpXV0kbGFiZWw8LUZBTFNFCiAgbTE8LXJlYzIuZGUudmFsc1tbaV1dW3JlYzIuZGUudmFsc1tbaV1dJGxvZzJGb2xkQ2hhbmdlPjEuNSwgJ2dlbmVJRCddWzE6MTBdCiAgbTI8LXJlYzIuZGUudmFsc1tbaV1dW3JlYzIuZGUudmFsc1tbaV1dJGxvZzJGb2xkQ2hhbmdlPCgtMS41KSwgJ2dlbmVJRCddWzE6MTBdCiAgbTwtd2hpY2gocmVjMi5kZS52YWxzW1tpXV0kZ2VuZUlEJWluJXVuaW9uKG0xLG0yKSkKICBmb3IoaiBpbiBtKXsKICAgIGlmKGFicyhyZWMyLmRlLnZhbHNbW2ldXSRsb2cyRm9sZENoYW5nZVtqXSk+MS41JnJlYzIuZGUudmFsc1tbaV1dJHBhZGpbal08MC4xKXsKICAgICAgcmVjMi5kZS52YWxzW1tpXV0kbGFiZWxbal08LVRSVUUKICAgIH0KICB9CiAgdm9sLnBsb3RzW1tpXV08LWdncGxvdChyZWMyLmRlLnZhbHNbW2ldXSwgYWVzKCB4PWxvZzJGb2xkQ2hhbmdlLCB5PSgtbG9nMTAocGFkaikpLCBjb2xvcj1Hcm91cCkpK3NjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYyhjb2xvci5jb21ib1ssaV0sICdncmF5NzAnKSkrZ2VvbV9wb2ludChzaXplPTAuMjQpK2dlb21fdGV4dF9yZXBlbChkYXRhID0gcmVjMi5kZS52YWxzW1tpXV1bd2hpY2gocmVjMi5kZS52YWxzW1tpXV0kbGFiZWwpLF0sIGFlcyggeD1sb2cyRm9sZENoYW5nZSwgeT0oLWxvZzEwKHBhZGopKSwgbGFiZWw9Z2VuZUlEKSwgc2l6ZT0xLjc2LCBzaG93LmxlZ2VuZD1GQUxTRSkrdGhlbWVfcHViK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDEuNSwgc2l6ZT0wLjI0KStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAtMS41LCBzaXplPTAuMjQpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDEsIHNpemU9MC4yNCkreGxhYigibG9nMiBmb2xkIGNoYW5nZSIpK3lsYWIoIi1sb2cxMCBwLWFkanVzdGVkIHZhbHVlIikrZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0xLjUpKSkKfQoKdm9sLnBsb3RzW1sxXV0gKyB2b2wucGxvdHNbWzJdXSArdm9sLnBsb3RzW1szXV0gKyBwbG90X2Fubm90YXRpb24odGFnX2xldmVscyA9ICdBJykrcGxvdF9sYXlvdXQobmNvbCA9IDIpCgpgYGAKICAKICBSTkEtc2VxOgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuMiwgZmlnLndpZHRoPTN9CmxldmVsczwtc29ydCh1bmlxdWUocm5hMmRfZGF5N1ssMV0pKQpwYWlyd2lzZS5jb21ibzwtY29tYm4obGV2ZWxzLCAyKQpjb2xvci5jb21ibzwtY29tYm4oY29sb3VyX2NvZGUkRGlldCwgMikKcm5hMi5kZS52YWxzPC1saXN0KCkKcm5hMi5lZC52YWxzPC1saXN0KCkKdm9sLnBsb3RzPC1saXN0KCkKREVHPC1saXN0KCkKZm9yKGkgaW4gMTpkaW0ocGFpcndpc2UuY29tYm8pWzJdKXsKICBkczwtcm5hMmRfZGF5N1t3aGljaChybmEyZF9kYXk3WywxXSVpbiVwYWlyd2lzZS5jb21ib1ssaV0pLF0KICBkcyREaWV0PC1hcy5jaGFyYWN0ZXIoZHMkRGlldCkKICBkdDwtcm5hMl9kYXk3Wyxyb3duYW1lcyhkcyldCiAgZHRmPC1yZWNvUmRzZXEudHJhbnNmb3JtKGR0LGRzKQogIHJuYTIuZGUudmFsc1tbaV1dIDwtIHJlY29SZHNlcS5ERShkdCwgZHMsIHRvb2wgPSAnREVTZXEyJykKICBybmEyLmVkLnZhbHNbW2ldXSA8LSByZWNvUmRzZXEuREUoZHQsIGRzLCB0b29sID0gJ2VkZ2VSJykKICByb3duYW1lcyhybmEyLmRlLnZhbHNbW2ldXSkgPC0gcm5hMi5kZS52YWxzW1tpXV0kZ2VuZUlECiAgcm93bmFtZXMocm5hMi5lZC52YWxzW1tpXV0pIDwtIHJuYTIuZWQudmFsc1tbaV1dJGdlbmVJRAogIGRlc2VxLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJuYTIuZGUudmFsc1tbaV1dLCBwID0gMC4xKQogIGVkZ2VyLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJuYTIuZWQudmFsc1tbaV1dLCBwID0gMC4xKQogIERFR1tbaV1dPC1kYXRhLmZyYW1lKHJvdy5uYW1lcz1pbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSxnZW5lSUQ9aW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksbG9nMkZvbGRjaGFuZ2U9cm5hMi5kZS52YWxzW1tpXV1baW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksIDRdLCBwYWRqPXJuYTIuZGUudmFsc1tbaV1dW2ludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLCA3XSkKICByaWJvc29tYWw8LWMoZ3JlcCgicnJzIiwgcm93bmFtZXMoREVHW1tpXV0pKSwgZ3JlcCgicnJsIiwgcm93bmFtZXMoREVHW1tpXV0pKSkKICBpZihsZW5ndGgocmlib3NvbWFsKT4wKXsKICAgICAgREVHW1tpXV08LURFR1tbaV1dWy1yaWJvc29tYWwsXQogIH0KICBERUdbW2ldXSRnZW5lSUQ8LWFzLmNoYXJhY3RlcihERUdbW2ldXSRnZW5lSUQpCiAgcm5hMi5kZS52YWxzW1tpXV08LXJuYTIuZGUudmFsc1tbaV1dW2NvbXBsZXRlLmNhc2VzKHJuYTIuZGUudmFsc1tbaV1dKSxdCiAgcm5hMi5kZS52YWxzW1tpXV0kR3JvdXA8LSdOb25lJwogIHJuYTIuZGUudmFsc1tbaV1dJEdyb3VwWyB3aGljaChybmEyLmRlLnZhbHNbW2ldXSRsb2cyRm9sZENoYW5nZT4xLjUmcm5hMi5kZS52YWxzW1tpXV0kcGFkajwwLjEpXTwtcGFzdGUwKCJ1cHJlZ3VsYXRlZC5pbi4iLCBhcy5jaGFyYWN0ZXIoc29ydCh1bmlxdWUoZHMkRGlldCkpWzJdKSkKICBybmEyLmRlLnZhbHNbW2ldXSRHcm91cFsgd2hpY2gocm5hMi5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2U8KC0xLjUpJnJuYTIuZGUudmFsc1tbaV1dJHBhZGo8MC4xKV08LXBhc3RlMCgidXByZWd1bGF0ZWQuaW4uIiwgc29ydCh1bmlxdWUoZHMkRGlldCkpWzFdKQogcm5hMi5kZS52YWxzW1tpXV0kR3JvdXA8LWZhY3RvcihybmEyLmRlLnZhbHNbW2ldXSRHcm91cCwgbGV2ZWxzID0gYyhwYXN0ZTAoInVwcmVndWxhdGVkLmluLiIsIGFzLmNoYXJhY3Rlcihzb3J0KHVuaXF1ZShkcyREaWV0KSlbMV0pKSwgcGFzdGUwKCJ1cHJlZ3VsYXRlZC5pbi4iLCBzb3J0KHVuaXF1ZShkcyREaWV0KSlbMl0pLCAnTm9uZScgKSkKICBybmEyLmRlLnZhbHNbW2ldXSRsYWJlbDwtRkFMU0UKICBtMTwtcm5hMi5kZS52YWxzW1tpXV1bcm5hMi5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2U+MS41LCAnZ2VuZUlEJ11bMToxMF0KICBtMjwtcm5hMi5kZS52YWxzW1tpXV1bcm5hMi5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2U8KC0xLjUpLCAnZ2VuZUlEJ11bMToxMF0KICBtPC13aGljaChybmEyLmRlLnZhbHNbW2ldXSRnZW5lSUQlaW4ldW5pb24obTEsbTIpKQogIGZvcihqIGluIG0pewogICAgaWYoYWJzKHJuYTIuZGUudmFsc1tbaV1dJGxvZzJGb2xkQ2hhbmdlW2pdKT4xLjUmcm5hMi5kZS52YWxzW1tpXV0kcGFkaltqXTwwLjEpewogICAgICBybmEyLmRlLnZhbHNbW2ldXSRsYWJlbFtqXTwtVFJVRQogICAgfQogIH0KICB2b2wucGxvdHNbW2ldXTwtZ2dwbG90KHJuYTIuZGUudmFsc1tbaV1dLCBhZXMoIHg9bG9nMkZvbGRDaGFuZ2UsIHk9KC1sb2cxMChwYWRqKSksIGNvbG9yPUdyb3VwKSkrc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKGNvbG9yLmNvbWJvWyxpXSwgJ2dyYXk3MCcpKStnZW9tX3BvaW50KHNpemU9MC4yNCkrZ2VvbV90ZXh0X3JlcGVsKGRhdGEgPSBybmEyLmRlLnZhbHNbW2ldXVt3aGljaChybmEyLmRlLnZhbHNbW2ldXSRsYWJlbCksXSwgYWVzKCB4PWxvZzJGb2xkQ2hhbmdlLCB5PSgtbG9nMTAocGFkaikpLCBsYWJlbD1nZW5lSUQpLCBzaXplPTEuNzYsIHNob3cubGVnZW5kPUZBTFNFKSt0aGVtZV9wdWIrZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMS41LCBzaXplPTAuMjQpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IC0xLjUsIHNpemU9MC4yNCkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSwgc2l6ZT0wLjI0KSt4bGFiKCJsb2cyIGZvbGQgY2hhbmdlIikreWxhYigiLWxvZzEwIHAtYWRqdXN0ZWQgdmFsdWUiKStndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTEuNSkpKQp9Cgp2b2wucGxvdHNbWzFdXSArIHZvbC5wbG90c1tbMl1dICt2b2wucGxvdHNbWzNdXSArIHBsb3RfYW5ub3RhdGlvbih0YWdfbGV2ZWxzID0gJ0EnKStwbG90X2xheW91dChuY29sID0gMikKCgoKYGBgCgoKIyMgSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgb24gZmluYWwgZGF5IHVzaW5nIERFR3MKCiAgV2Ugd2FudCB0byBjaGVjayB3aGV0aGVyIGluZm9ybWF0aW9uIGFib3V0IGRpZXQgZ3JvdXBzIHByaW9yIHRvIHN3aXRjaCBjYW4gYmUgcmV0cmlldmVkIGF0IGRheSAyMCAtIGkuZSAxMyBkYXlzIGFmdGVyIHRoZSBzd2l0Y2guIEZvciB0aGlzLCB3ZSB1c2UgZGlldCBzaWduYXR1cmUgZ2VuZXMgaWRlbnRpZmllZCBiZWZvcmUgdGhlIHN3aXRjaCAoREVHcykgdG8gaGllcmFyY2hpY2FsbHkgY2x1c3RlciB0aGUgZ3JvdXBzLiBXZSBjYW4gYWxtb3N0IHBlcmZlY3RseSBjbGFzc2lmeSBncm91cHMgdXNpbmcgUmVjb3JkLXNlcSBkYXRhLCB3aGlsZSBmb3IgUk5BLXNlcSwgdGhlIGdyb3VwcyBjb252ZXJnZS4gCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuMiwgZmlnLndpZHRoPTN9CnJlYzJkX2RheTIwPC1yZWMyZFtyZWMyZCREYXk9PTIwLF0KcmVjMl9kYXkyMDwtcmVjMlsscm93bmFtZXMocmVjMmRfZGF5MjApXQpyZWMyX2RheTIwX3RmPC1yZWNvUmRzZXEudHJhbnNmb3JtKHJlYzJfZGF5MjAsIHJlYzJkX2RheTIwKQoKY29sczwtIGNvbG9yUmFtcFBhbGV0dGUoYygiZG9kZ2VyYmx1ZTQiLCAid2hpdGUiLCJ2aW9sZXRyZWQ0IikpKDI1NikKcmlib3NvbWFsPC1jKGdyZXAoInJycyIsIHJlYzIuZ2xvYmFsLkRFRyRnZW5lSUQpLCBncmVwKCJycmwiLCByZWMyLmdsb2JhbC5ERUckZ2VuZUlEKSkKaWYobGVuZ3RoKHJpYm9zb21hbCk+MCl7CiAgcmVjMi5nbG9iYWwuREVHPC1yZWMyLmdsb2JhbC5ERUdbLXJpYm9zb21hbCxdCn0KcmVjMi5nZW5lU2hvcnRMaXN0PC11bmlxdWUoYyhyZWMyLmdsb2JhbC5ERUdbd2hpY2gocmVjMi5nbG9iYWwuREVHJGxvZzJGb2xkQ2hhbmdlLm1heF9GQz4yLjUpLCAxXVsxOjEwXSwgcmVjMi5nbG9iYWwuREVHW3doaWNoKHJlYzIuZ2xvYmFsLkRFRyRsb2cyRm9sZENoYW5nZS5tYXhfU0M+Mi41KSwgMV1bMToxMF0scmVjMi5nbG9iYWwuREVHW3doaWNoKHJlYzIuZ2xvYmFsLkRFRyRsb2cyRm9sZENoYW5nZS5tYXhfU0M8KC0yLjUpJnJlYzIuZ2xvYmFsLkRFRyRsb2cyRm9sZENoYW5nZS5tYXhfRkM8KC0yLjUpKSwgMV1bMToxMF0pKQpkaGVhdG1hcDwtYXMuZGF0YS5mcmFtZSh0KGFwcGx5KHJlYzJfZGF5MjBfdGZbcmVjMi5nZW5lU2hvcnRMaXN0LF0sIDEsIHpzY29yZXN0YW5kYXJkaXplKSkpCmhlYXRtYXAucmVjMjwtcGhlYXRtYXAoZGhlYXRtYXAsIGFubm90YXRpb25fY29sID0gcmVjMmRfZGF5MjBbLDEsIGRyb3A9RkFMU0VdLCBhbm5vdGF0aW9uX2NvbG9ycz1jb2xvdXJfY29kZSwgdHJlZWhlaWdodF9yb3c9MCwgZm9udHNpemUgPSA1LCBmb250c2l6ZV9yb3cgPSA1LCBmb250c2l6ZV9jb2wgPSA1LCBjbHVzdGVyX3Jvd3MgPSBGQUxTRSwgc2hvd19jb2xuYW1lcyA9IEZBTFNFLGNvbG9yID0gY29scyxmb250c2l6ZV9udW1iZXI9NSwgbWFpbj0nSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgb2YgUmVjb3JkLXNlcSBkYXRhIG9uIGRheSAyMCBiYXNlZCBvbiBkaWV0IHNpZ25hdHVyZSBnZW5lcycpCmhlYXRtYXAuZ2VuZWxpc3Q8LXJlYzJfZGF5MjBfdGZbcmVjMi5nZW5lU2hvcnRMaXN0LF0KY29sbmFtZXMoaGVhdG1hcC5nZW5lbGlzdCk8LWFzLmNoYXJhY3RlcihyZWMyZF9kYXkyMCREaWV0KQoKcm5hMmRfZGF5MjA8LXJuYTJkW3JuYTJkJERheT09MjAsXQpybmEyX2RheTIwPC1ybmEyWyxyb3duYW1lcyhybmEyZF9kYXkyMCldCnJuYTJfZGF5MjBfdGY8LXJlY29SZHNlcS50cmFuc2Zvcm0ocm5hMl9kYXkyMCwgcm5hMmRfZGF5MjApCnJuYTIuZ2VuZVNob3J0TGlzdDwtdW5pcXVlKGMocm5hMi5ERUdbcm5hMi5ERUckbG9nMkZvbGRDaGFuZ2UuRmF0X3ZzX0Nob3c+Mi41LCAxXVsxOjEwXSwgcm5hMi5ERUdbcm5hMi5ERUckbG9nMkZvbGRDaGFuZ2UuU3RhcmNoX3ZzX0Nob3c+Mi41LCAxXVsxOjEwXSwgcm5hMi5ERUdbd2hpY2gocm93TWVhbnMocm5hMi5ERUdbLDM6NF0pPCgtMi41KSksIDFdWzE6MTBdKSkKY29sczwtIGNvbG9yUmFtcFBhbGV0dGUoYygiZG9kZ2VyYmx1ZTQiLCAid2hpdGUiLCJ2aW9sZXRyZWQ0IikpKDI1NikKZGhlYXRtYXA8LWFzLmRhdGEuZnJhbWUodChhcHBseShybmEyX2RheTIwX3RmW3JuYTIuZ2VuZVNob3J0TGlzdCxdLCAxLCB6c2NvcmVzdGFuZGFyZGl6ZSkpKQpoZWF0bWFwLnJuYTI8LXBoZWF0bWFwKGRoZWF0bWFwLCBhbm5vdGF0aW9uX2NvbCA9IHJuYTJkX2RheTIwWywxLCBkcm9wPUZBTFNFXSwgYW5ub3RhdGlvbl9jb2xvcnM9Y29sb3VyX2NvZGUsIHRyZWVoZWlnaHRfcm93PTAsIGZvbnRzaXplID0gNSwgZm9udHNpemVfcm93ID0gNSwgZm9udHNpemVfY29sID0gNSwgY2x1c3Rlcl9yb3dzID0gRkFMU0UsIHNob3dfY29sbmFtZXMgPSBGQUxTRSwgY29sb3IgPSBjb2xzLGZvbnRzaXplX251bWJlcj01LCBtYWluPSdIaWVyYXJjaGljYWwgY2x1c3RlcmluZyBvZiBSTkEtc2VxIGRhdGEgb24gZGF5IDIwIGJhc2VkIG9uIGRpZXQgc2lnbmF0dXJlIGdlbmVzJykKaGVhdG1hcC5nZW5lbGlzdDwtcm5hMl9kYXkyMF90ZltybmEyLmdlbmVTaG9ydExpc3QsXQpjb2xuYW1lcyhoZWF0bWFwLmdlbmVsaXN0KTwtYXMuY2hhcmFjdGVyKHJuYTJkX2RheTIwJERpZXQpCmBgYAoKIyMgRWNvY3ljIGFuYWx5c2lzOgoKICBXZSBjcmVhdGUgZW5yaWNobWVudCBwbG90cyBmb3IgdG9wIGRpZmZlcmVudGlhbGx5IHJlZ3VsYXRlZCBwYXRod2F5cyBpZGVudGlmaWVkIGJ5IEVjb2N5YyB1c2luZyB0aGUgcGFpcndpc2UgREVHIGxpc3RzIGdlbmVyYXRlZCBoZXJlLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjIsIGZpZy53aWR0aD0zfQpDaG93WFN0YXJjaC5wYXRod2F5PC1hcy5kYXRhLmZyYW1lKHJlYWQudGFibGUoImRhdGEvQ2hvdy5TdGFyY2gucGF0aHdheS50eHQiLCBoZWFkZXI9VFJVRSwgc2VwID0gJ1x0JykpCkNob3dYU3RhcmNoLnBhdGh3YXkkcC52YWx1ZXNbQ2hvd1hTdGFyY2gucGF0aHdheSRncm91cD09J2VucmljaGVkLmluLkNob3cnXTwtbG9nMTAoQ2hvd1hTdGFyY2gucGF0aHdheSRwLnZhbHVlc1tDaG93WFN0YXJjaC5wYXRod2F5JGdyb3VwPT0nZW5yaWNoZWQuaW4uQ2hvdyddKSooLTEpCkNob3dYU3RhcmNoLnBhdGh3YXkkcC52YWx1ZXNbQ2hvd1hTdGFyY2gucGF0aHdheSRncm91cD09J2VucmljaGVkLmluLlN0YXJjaCddPC1sb2cxMChDaG93WFN0YXJjaC5wYXRod2F5JHAudmFsdWVzW0Nob3dYU3RhcmNoLnBhdGh3YXkkZ3JvdXA9PSdlbnJpY2hlZC5pbi5TdGFyY2gnXSkKQ2hvd1hTdGFyY2gucGF0aHdheTwtQ2hvd1hTdGFyY2gucGF0aHdheVtvcmRlcihDaG93WFN0YXJjaC5wYXRod2F5JHAudmFsdWVzKSxdCkNob3dYU3RhcmNoLnBhdGh3YXkucGxvdDwtZ2dwbG90KENob3dYU3RhcmNoLnBhdGh3YXksIGFlcyh4PVBhdGh3YXksIHk9cC52YWx1ZXMsIHNpemU9bnVtYmVyLm9mLmdlbmVzLCBjb2xvdXI9Z3JvdXAgKSkrZ2VvbV9wb2ludCgpK2Nvb3JkX2ZsaXAoKSt0aGVtZV9wdWIrc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYygxLDMpKStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAoLTEuMzAxMDMpLCBzaXplPTAuMjQpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMS4zMDEwMywgc2l6ZT0wLjI0KSt4bGFiKCIiKSt5bGFiKCItbG9nMTAgcC1hZGp1c3RlZCB2YWx1ZSIpKyBsYWJzKHNpemUgPSAiR2VuZXMgZGV0ZWN0ZWQiKStzY2FsZV94X2Rpc2NyZXRlKGxpbWl0cz1DaG93WFN0YXJjaC5wYXRod2F5JFBhdGh3YXkpK3NjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKGFzLmNoYXJhY3Rlcihjb2xvdXJfY29kZSREaWV0W2MoMSwzKV0pKSkKCkNob3dYU3RhcmNoLnBhdGh3YXkucGxvdCtwbG90X2Fubm90YXRpb24oKQoKQ2hvd1hGYXQucGF0aHdheTwtYXMuZGF0YS5mcmFtZShyZWFkLnRhYmxlKCJkYXRhL0Nob3cuRmF0LnBhdGh3YXkudHh0IiwgaGVhZGVyPVRSVUUsIHNlcCA9ICdcdCcpKQpDaG93WEZhdC5wYXRod2F5JHAudmFsdWVzW0Nob3dYRmF0LnBhdGh3YXkkZ3JvdXA9PSdlbnJpY2hlZC5pbi5DaG93J108LWxvZzEwKENob3dYRmF0LnBhdGh3YXkkcC52YWx1ZXNbQ2hvd1hGYXQucGF0aHdheSRncm91cD09J2VucmljaGVkLmluLkNob3cnXSkqKC0xKQpDaG93WEZhdC5wYXRod2F5JHAudmFsdWVzW0Nob3dYRmF0LnBhdGh3YXkkZ3JvdXA9PSdlbnJpY2hlZC5pbi5GYXQnXTwtbG9nMTAoQ2hvd1hGYXQucGF0aHdheSRwLnZhbHVlc1tDaG93WEZhdC5wYXRod2F5JGdyb3VwPT0nZW5yaWNoZWQuaW4uRmF0J10pCkNob3dYRmF0LnBhdGh3YXk8LUNob3dYRmF0LnBhdGh3YXlbb3JkZXIoQ2hvd1hGYXQucGF0aHdheSRwLnZhbHVlcyksXQpDaG93WEZhdC5wYXRod2F5LnBsb3Q8LWdncGxvdChDaG93WEZhdC5wYXRod2F5LCBhZXMoeD1QYXRod2F5LCB5PXAudmFsdWVzLCBzaXplPW51bWJlci5vZi5nZW5lcywgY29sb3VyPWdyb3VwICkpK2dlb21fcG9pbnQoKStjb29yZF9mbGlwKCkrdGhlbWVfcHViK3NjYWxlX3NpemVfY29udGludW91cyhyYW5nZSA9IGMoMSwzKSkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gKC0xLjMwMTAzKSwgc2l6ZT0wLjI0KStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDEuMzAxMDMsIHNpemU9MC4yNCkreGxhYigiIikreWxhYigiLWxvZzEwIHAtYWRqdXN0ZWQgdmFsdWUiKSsgbGFicyhzaXplID0gIkdlbmVzIGRldGVjdGVkIikrc2NhbGVfeF9kaXNjcmV0ZShsaW1pdHM9Q2hvd1hGYXQucGF0aHdheSRQYXRod2F5KStzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhhcy5jaGFyYWN0ZXIoY29sb3VyX2NvZGUkRGlldFtjKDEsMildKSkpCgpDaG93WEZhdC5wYXRod2F5LnBsb3QrcGxvdF9hbm5vdGF0aW9uKCkKCkZhdFhTdGFyY2gucGF0aHdheTwtYXMuZGF0YS5mcmFtZShyZWFkLnRhYmxlKCJkYXRhL0ZhdC5TdGFyY2gucGF0aHdheS50eHQiLCBoZWFkZXI9VFJVRSwgc2VwID0gJ1x0JykpCkZhdFhTdGFyY2gucGF0aHdheSRwLnZhbHVlc1tGYXRYU3RhcmNoLnBhdGh3YXkkZ3JvdXA9PSdlbnJpY2hlZC5pbi5GYXQnXTwtbG9nMTAoRmF0WFN0YXJjaC5wYXRod2F5JHAudmFsdWVzW0ZhdFhTdGFyY2gucGF0aHdheSRncm91cD09J2VucmljaGVkLmluLkZhdCddKSooLTEpCkZhdFhTdGFyY2gucGF0aHdheSRwLnZhbHVlc1tGYXRYU3RhcmNoLnBhdGh3YXkkZ3JvdXA9PSdlbnJpY2hlZC5pbi5TdGFyY2gnXTwtbG9nMTAoRmF0WFN0YXJjaC5wYXRod2F5JHAudmFsdWVzW0ZhdFhTdGFyY2gucGF0aHdheSRncm91cD09J2VucmljaGVkLmluLlN0YXJjaCddKQpGYXRYU3RhcmNoLnBhdGh3YXk8LUZhdFhTdGFyY2gucGF0aHdheVtvcmRlcihGYXRYU3RhcmNoLnBhdGh3YXkkcC52YWx1ZXMpLF0KRmF0WFN0YXJjaC5wYXRod2F5LnBsb3Q8LWdncGxvdChGYXRYU3RhcmNoLnBhdGh3YXksIGFlcyh4PVBhdGh3YXksIHk9cC52YWx1ZXMsIHNpemU9bnVtYmVyLm9mLmdlbmVzLCBjb2xvdXI9Z3JvdXAgKSkrZ2VvbV9wb2ludCgpK2Nvb3JkX2ZsaXAoKSt0aGVtZV9wdWIrc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYygxLDMpKStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAoLTEuMzAxMDMpLCBzaXplPTAuMjQpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMS4zMDEwMywgc2l6ZT0wLjI0KSt4bGFiKCIiKSt5bGFiKCItbG9nMTAgcC1hZGp1c3RlZCB2YWx1ZSIpKyBsYWJzKHNpemUgPSAiR2VuZXMgZGV0ZWN0ZWQiKStzY2FsZV94X2Rpc2NyZXRlKGxpbWl0cz1GYXRYU3RhcmNoLnBhdGh3YXkkUGF0aHdheSkrc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoYXMuY2hhcmFjdGVyKGNvbG91cl9jb2RlJERpZXRbYygyLDMpXSkpKQoKRmF0WFN0YXJjaC5wYXRod2F5LnBsb3QrcGxvdF9hbm5vdGF0aW9uKCkKCgpgYGAKIyBDaGVja2luZyByZXByb2R1Y2liaWxpdHkgb2YgY2xhc3NpZmllciBnZW5lcyAoREVHcykKCiBXZSB3YW50IHRvIGNvbmZpcm0gdGhhdCB0aGVyZSBpcyBhbiBvdmVybGFwIGFtb25nIHRoZSBERUdzICBpZGVudGlmaWVkIGluIHRoZSB0d28gZXhwZXJpbWVudGFsIHJlcGxpY2F0ZXMsIGFuZCB0aGUgZGlyZWN0aW9uIG9mIGRpZmZlcmVudGlhbCByZWd1bGF0aW9uIGlzIGNvbnNpc3RlbnQuIFdlIHVzZSB0aGUgZ2VuZXMgdGhhdCBhcmUgdXByZWd1bGF0ZWQvZG93bnJlZ3VsYXRlZCBpbiB0aGUgQ2hvdyBncm91cCBjb21wYXJlZCB0byB0aGUgU3RhcmNoIGdyb3VwIG9uIGRheSA3LgogCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQgPSAzLCBmaWcud2lkdGggPSAzLCBmaWcuYWxpZ24gPSAiY2VudGVyIn0KZGVzZXEuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMS5kZS52YWxzW1syXV0sIHAgPSAwLjEpCmVkZ2VyLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzEuZWQudmFsc1tbMl1dLCBwID0gMC4xKQpyZWMxLkNob3cudi5TdGFyY2guREVHPC1kYXRhLmZyYW1lKHJvdy5uYW1lcyA9IGludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLGdlbmVJRD1pbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSwgbG9nMkZDPXJlYzEuZGUudmFsc1tbMl1dW2ludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLDRdKQoKZGVzZXEuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMi5kZS52YWxzW1syXV0sIHAgPSAwLjEpCmVkZ2VyLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzIuZWQudmFsc1tbMl1dLCBwID0gMC4xKQpyZWMyLkNob3cudi5TdGFyY2guREVHPC1kYXRhLmZyYW1lKHJvdy5uYW1lcyA9IGludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLGdlbmVJRD1pbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSwgbG9nMkZDPXJlYzIuZGUudmFsc1tbMl1dW2ludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLDRdKQoKcGxvdChldWxlcihsaXN0KHJlYzEgPSByZWMxLkNob3cudi5TdGFyY2guREVHJGdlbmVJRCwgcmVjMiA9IHJlYzIuQ2hvdy52LlN0YXJjaC5ERUckZ2VuZUlEKSkgLCBxdWFudGl0aWVzPVRSVUUpCgpgYGAKICBGaW5hbGx5LCB3ZSBwbG90IGEgY29ycmVsYXRpb24gcGxvdCBiYXNlZCBvbiB0aGUgbG9nMkZDIGRldGVjdGVkIGZvciBERUdzIGZvciB0aGUgdHdvIGV4cGVyaW1lbnRzLCBhbmQgZXN0aW1hdGUgdGhlIG51bWJlciBvZiBERUdzIHJlZ3VsYXRlZCBpbiBhIHNpbWlsYXIgZGlyZWN0aW9uLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjIsIGZpZy53aWR0aD0zfQogREVHLmNvbXBhcmU8LWRhdGEuZnJhbWUoZ2VuZUlEPWludGVyc2VjdChyZWMxLkNob3cudi5TdGFyY2guREVHJGdlbmVJRCwgcmVjMi5DaG93LnYuU3RhcmNoLkRFRyRnZW5lSUQpKQpERUcuY29tcGFyZSRnZW5lSUQ8LWFzLmNoYXJhY3RlcihERUcuY29tcGFyZSRnZW5lSUQpCkRFRy5jb21wYXJlJHJlYzFfbG9nMkZDPC1yZWMxLkNob3cudi5TdGFyY2guREVHW0RFRy5jb21wYXJlJGdlbmVJRCwyXQpERUcuY29tcGFyZSRyZWMyX2xvZzJGQzwtcmVjMi5DaG93LnYuU3RhcmNoLkRFR1tERUcuY29tcGFyZSRnZW5lSUQsMl0KREVHLmNvbXBhcmU8LURFRy5jb21wYXJlW2NvbXBsZXRlLmNhc2VzKERFRy5jb21wYXJlKSwgXQpyMjwtcm91bmQoY29yKERFRy5jb21wYXJlJHJlYzFfbG9nMkZDLCBERUcuY29tcGFyZSRyZWMyX2xvZzJGQyleMiwyKQpuPC1yb3VuZChsZW5ndGgod2hpY2goREVHLmNvbXBhcmUkcmVjMV9sb2cyRkMqREVHLmNvbXBhcmUkcmVjMl9sb2cyRkM+MCkpKjEwMC9sZW5ndGgoREVHLmNvbXBhcmUkZ2VuZUlEKSkKREVHLnNjYXR0ZXJwbG90PC1nZ3Bsb3QoREVHLmNvbXBhcmUsIGFlcyh5PXJlYzFfbG9nMkZDLCB4PXJlYzJfbG9nMkZDKSkrZ2VvbV9wb2ludChzaXplPTAuNDgsIGFlcyhjb2xvdXI9J2dyYXkxMCcpKStnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nLCBzZSA9IEZBTFNFLCBzaXplPTAuNDgpK3RoZW1lX3B1YitnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkreGxhYigibG9nMiBmb2xkIGNoYW5nZSBvZiBERUdzIGRldGVjdGVkIGluIHRyYW5zaWVudCBkaWV0IDIiKSt5bGFiKCJjb3JyZXNwb25kaW5nIGxvZzIgZm9sZCBjaGFuZ2UgaW4gdHJhbnNpZW50IGRpZXQgMSIpK2Fubm90YXRlKCJ0ZXh0IiwgIHg9SW5mLCB5ID0gSW5mLCBsYWJlbCA9IHBhc3RlMCgiUl4yID0iLGFzLmNoYXJhY3RlcihyMiksICIgXG4gREVHcyByZWd1bGF0ZWQgaW4gdGhlIHNhbWUgZGlyZWN0aW9uID0gIixhcy5jaGFyYWN0ZXIobiksICIlIiksIHZqdXN0PTEsIGhqdXN0PTEsIHNpemU9Mykrc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoJ2dyYXkxMCcpLCBndWlkZT0nbm9uZScpK2dndGl0bGUoIkNvcnJlbGF0aW9uIG9mIG92ZXJsYXBwaW5nIERFR3MgZGV0ZWN0ZWQgaW4gYm90aCBleHBlcmltZW50cyIpCkRFRy5zY2F0dGVycGxvdCtwbG90X2Fubm90YXRpb24oKQpgYGAKIyBJbmZvcm1hdGlvbiBhYm91dCBSIHNlc3Npb24KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kc2Vzc2lvbkluZm8oKQpgYGA=